point:仅使用常数空间的一趟扫描算法
一、什么是荷兰国旗问题
对只含有三种数字的数组进行分类或者排序称为荷兰国旗问题
二、如何解决此类问题
解决这类问题借助快速排序partition过程的一趟扫描法。(重点在于设计循环不变量)
partition的过程就是随机选择一个基准值,然后经过一次扫描通过交换不同位置的元素使得数组大小分成三部分,分别是:小于基准值,等于基准值和大于基准值。
那为什么只需要一次就够呢?(即无需递归实现)
因为题目中的三个数是无差别的,也就是说0和0不需要排序
三、如何实现
实现过程:
1.定义分区的边界
首先定义两个指针p0和p2分别指向头和尾
然后是循环变量i用于遍历数组
划分:
all in [0, p0) = 0
all in [p0, i) = 1
all in (p2, len - 1] = 2
需要注意的是,区间划分不同,循环的条件也不同
2.在遍历开始前这三个区间都为空
3.例如2,0,2,1,1,0
i从0开始遍历,遇到2。2应该在最后一个区间,所以该2与p2所指的数进行交换。
由于所有的2都要在p2之后,所以p2–
而交换过来的数是0,而0都在p0指针之前,所以p0++
最后i向前移动,移动后遇到的数还是0。为了满足p0的定义,p0++
然后i向前,遇到2,所以与p2所指位置的数交换,p2–
i继续向前遇到1,1既不在第一个区间也不在最后一个区间,所以不需要移动,只需要让i继续向前,同理。
最后i>p2,所以结束循环。
import java.util.Arrays;
public class Solution {
public void sortColors(int[] nums) {
int len = nums.length;
if (len < 2) {
return;
}
// 循环终止条件是 i > two,那么循环可以继续的条件是 i <= two
// 为了保证初始化的时候 [0, p0) 为空,设置 p0 = 0,
// 所以下面遍历到 0 的时候,先交换,再加
int p0 = 0;
// 为了保证初始化的时候 (p2, len - 1] 为空,设置 p2 = len-1
int p2 = len-1;
int i = 0;
// 当 i > p2 上面的三个子区间正好覆盖了全部数组
// 因此,循环可以继续的条件是 i <= two
while (i <= two) {
if (nums[i] == 0) {
swap(nums, i, p0);
p0++;
i++;
} else if (nums[i] == 1) {
i++;
} else {
swap(nums, i, two);
p2--;
}
}
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}