分类算法 - Three Way Partition(三分法)
Three Way Partition(三分法)算法练手

介绍

Three Way Partition(三分法)可以在O(n)的时间复杂度内将一个数据分为3个部分,需要注意的是三分法是分类,并不是排序,例如有一个数组为3, 5, 2, 7, 6, 4, 2, 8, 8, 9, 0,可以以4为中间值,将数组分为两个部分3, 2, 0, 2, 4, | 8, 7, 8, 9, 6, 5,也可以以4-7大小范围的值作为中间值,将数组分为三个部分3, 2, 0, 2, | 4, 6, 5, 7, | 8, 8, 9

代码参考自StackOverflowLeetCode题解

代码

func ThreeWayPartition(arr []int, midLeft int, midRight int) {
	// 定义三个指针
  // i用于标记处于中间范围的值,即 midLeft <= arr[i] <= midRight
	i, j, k := 0, 0, len(arr)-1
  
  // 过程类似快排,保证所有元素都遍历过去
  for j <= k {
    if arr[j] > midRight {
    	// 若j指向的值大于中间范围,则和k进行交换,保证大于范围的值位于右侧
      arr[j], arr[k] = arr[k], arr[j]
      // 这里只移动k,不移动j,目的是为了能在下一次循环中比较从k换过来的值
      k--
    } else if arr[j] < midLeft {
      // 若j指向的值小于中间范围,则和i进行交换,保证小于范围的值位于左侧
      arr[j], arr[i] = arr[i], arr[j]
      // 此时可以确认i是小于范围的,自信++
      i++
      // 换上来的数据也肯定是经过筛选,不用再次参与循环,自信++
      j++
    } else {
      // 若j指向的值位于中间范围,跳过
      j++
    }
  }
}

func main() {
	nums := []int{1,2,6,3,7,8,3,9,3,2}
	fmt.Println(nums) // 输出 1 2 6 3 7 8 3 9 3 2
	ThreeWayPartition(nums, 3, 6)
	fmt.Println(nums) // 输出 1 2 2 3 6 3 3 9 8 7
}

可以看到,分类后的数据被划分成三个区间1,2,2,|3,6,3,3|,9,8,7

如果只是想将数据分为两部分,且中间部分的值保持一致,例如将1,2,6,3,7,8,3,9,3,2,划分为1,2,2,|3,3,3|,9,8,7,6,则可以将传参的边界值都改为3

相关题目

324. 摆动排序 II

难度中等

给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。

你可以假设所有输入数组都可以得到满足题目要求的结果。

示例 1:

输入:nums = [1,5,1,1,6,4]
输出:[1,6,1,5,1,4]
解释:[1,4,1,5,1,6] 同样是符合题目要求的结果,可以被判题程序接受。

示例 2:

输入:nums = [1,3,2,2,3,1]
输出:[2,3,1,3,1,2]

提示:

  • 1 <= nums.length <= 5 * 104
  • 0 <= nums[i] <= 5000
  • 题目数据保证,对于给定的输入 nums ,总能产生满足题目要求的结果

**进阶:**你能用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现吗?

解:

// 快速选择算法
func partition (nums []int, k, left, right int) int {
	if left == right {
		return nums[left]
	}
	l, r := left, right
	p := nums[left]

	for l < r {
		// 右侧找比p小的值
		for l < r && nums[r] > p {
			r--
		}
		for l < r && nums[l] <= p {
			l++
		}
		if l >= r {
			break
		}
		nums[l], nums[r] = nums[r], nums[l]
	}

	nums[l], nums[left] = nums[left], nums[l]

	if l == k {
		return nums[l]
	} else if l < k {
		return partition(nums, k, l+1, right)
	} else {
		return partition(nums, k, left, l-1)
	}
}

// 使用快速选择算法返回第K大的数(K从0开始计数)
func KMax(nums []int, k int) int {
	return partition(nums, k, 0, len(nums)-1)
}

// 使用三分法将数组分为三份
func ThreeWayPartition(nums []int, target int) {
	i, j, k := 0, 0, len(nums)-1
	for j <= k {
		if nums[j] > target {
			nums[j], nums[k] = nums[k], nums[j]
			k--
		} else if nums[j] < target {
			nums[j], nums[i] = nums[i], nums[j]
			i++
			j++
		} else {
			j++
		}
	}
	return
}

func wiggleSort(nums []int)  {
	ans := make([]int, 0)
	// 向下取中间的序号,6取2、7取3这样
	midIndex := (len(nums) - 1) / 2
	// 获取中间元素
	midNum := KMax(nums, midIndex)
	// 以mid为中心三分数组
	ThreeWayPartition(nums, midNum)
	// 以中间序号为切分点,切分成两个数组,倒序插入两个数组的元素
	l, r := midIndex, len(nums) - 1
	for l >= 0 || r > midIndex {
		if l >= 0 {
			ans = append(ans, nums[l])
			l--
		}
		if r > midIndex {
			ans = append(ans, nums[r])
			r--
		}
	}

	copy(nums, ans)
}

最后修改于 2022-02-16