本文最后编辑于 前,其中的内容可能需要更新。
题目描述: 给定一个包含红色、白色和蓝色,一共 n
个元素的数组,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
进阶 :
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
示例 1: 1 2 输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]
示例 2: 1 2 输入:nums = [2,0,1] 输出:[0,1,2]
示例 3:
示例 4:
提示:
n == nums.length
1 <= n <= 300
nums[i]
为 0
、1
或 2
链接: https://leetcode-cn.com/problems/sort-colors
题目分析 1.计数 题目中要求使用原地算法,并且最好不用排序函数,而数字只有三种,我们可以很容易想到使用计数的方法。先遍历一次数组记录 0
和 1
的个数,剩下的就是 2
,之后对整个数组进行重写即可。一共需要两次遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : void sortColors (vector<int >& nums) { int red = 0 , white = 0 ; for (int i = 0 ; i < nums.size (); i++){ if (nums[i] == 0 ) red++; else if (nums[i] == 1 ) white++; } for (int i = 0 ; i < red; i++){ nums[i] = 0 ; } for (int i = red; i < red + white; i++){ nums[i] = 1 ; } for (int i = red + white; i < nums.size (); i++){ nums[i] = 2 ; } } };
2.双指针 由于只有三种数字,最后的结果中 0
一定在左边,2
一定在右边。那我们只进行一次遍历,每次将 0
放到左边,2
放到右边,最后剩下的 1
就都在中间,也就完成了。我们需要两个指针 p0
和 p2
分别指示 0
和 2
的边界位置。 注意我们是从左往右遍历,而 p2
是位于右边,因此与 p2
交换后,i
所指示的位置的新数字依然是没有处理过的,因此 i
不能自增。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : void sortColors (vector<int >& nums) { int p0 = 0 , p2 = nums.size ()-1 ; int i = 0 ; while (i <= p2){ if (nums[i] == 2 ){ swap (nums[i], nums[p2]); p2--; } else { if (nums[i] == 0 ){ swap (nums[i], nums[p0]); p0++; } i++; } } } };
3.官方题解 官方题解中还提到了其他同样使用指针的方法。一种是单指针的方法,先遍历一次处理 0
(遇到 0
就交换到最左边),再从 0
结束的位置遍历一次处理 1
,一共需要两次遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : void sortColors (vector<int >& nums) { int n = nums.size (); int ptr = 0 ; for (int i = 0 ; i < n; ++i) { if (nums[i] == 0 ) { swap (nums[i], nums[ptr]); ++ptr; } } for (int i = ptr; i < n; ++i) { if (nums[i] == 1 ) { swap (nums[i], nums[ptr]); ++ptr; } } } };
还有一种是另外一种双指针的方法,记录的是 0
和 1
的边界位置,放入 0
时两个指针同时右移(若有 1
被 0
替换还需在末尾补 1
),放入 1
时 p1
右移,这种方法也是一次遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : void sortColors (vector<int >& nums) { int n = nums.size (); int p0 = 0 , p1 = 0 ; for (int i = 0 ; i < n; ++i) { if (nums[i] == 1 ) { swap (nums[i], nums[p1]); ++p1; } else if (nums[i] == 0 ) { swap (nums[i], nums[p0]); if (p0 < p1) { swap (nums[i], nums[p1]); } ++p0; ++p1; } } } };
复杂度分析 以上的所有方法不管是两次遍历还是一次遍历,时间复杂度都是 $O(n)$,而作为原地算法,空间复杂度都是 $O(1)$。