题目:
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
解法一(计数):
根据题目中的提示,我们可以统计出数组中 0,1,2 的个数,再根据它们的数量,重写整个数组。这种方法较为简单,也很容易想到。遍历三次nums数组,依次记录0,1,2三个数的个数,然后再循环遍历一次nums数组,将所记录的0,1,2三个数以先添加0直至全部添加完,然后再添加1直至全部添加完,最后再前2全部添加完至nums数组中为止,虽然也能正确完成本题解算(时间复杂度O(n)均是单层循环),但是对nums的值进行了赋值和修改,并不是本题的最优解算方式,仅作为与后续解法方案的对比,如下为笔者代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
int length = nums.size();
int x = 0;
int y = 0;
int z = 0;
for(int i=0;i<length;i++){
if(nums[i]==0){
x++;
}
}
for(int i=0;i<length;i++){
if(nums[i]==1){
y++;
}
}
for(int i=0;i<length;i++){
if(nums[i]==2){
z++;
}
}
for(int i=0;i<length;i++){
if(x>0){
nums[i]=0;
x--;
continue;
}
if(y>0){
nums[i]=1;
y--;
continue;
}
if(z>0){
nums[i]=2;
z--;
continue;
}
}
}
};
解法二(单指针):
我们可以考虑对数组进行两次遍历,第一次遍历中,我们将数组中所有的0交换到数组的头部。在第二次遍历中,我们将数组中所有的1交换到头部的0之后。此时,所有的2都出现在数组的尾部,这样我们就是完成了排序。
具体地,我们使用一个指针ptr表示【头部】范围,ptr中存储了一个整数,表示数组nums从位置0到位置ptr-1都属于【头部】。ptr的初始值为0,表示还没有数处于【头部】。
在第一次遍历中,我们从左向右遍历整个数组,如果找到了0那么就需要将0与【头部】位置的元素进行交换,并将【头部】向后扩充一个位置。遍历结束之后,所有的0都被交换到【头部】的范围,并且【头部】只包含0。
在第二次遍历中,我们从【头部】开始,从左向有遍历整个数组,如果找到了1,那么就需要将1与【头部】位置的元素进行交换,并将【头部】向后扩充一个位置。在遍历结束之后,所有的1都被交换到【头部】的范围,并且都在0之后,此时2只出现在【头部】之外的位置,因此排序完成,如下为实现代码:
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;
}
}
}
};
-
时间复杂度:O(n),其中 n 是数组 nums 的长度。空间复杂度:O(1)。
解法三(双指针v1):
方法二需要进行两次遍历,那么我们是否可以仅使用一次遍历呢?我们可以额外使用一个指针,即使用两个指针分别用来交换 0 和 1。
具体地,我们用指针p0来交换0,p1来交换1,初始值都为0.当我们从左向右遍历整个数组时:
1、如果找到了1,那么将其与nums[p1]进行交换,并将p1向后移动一个位置,这与解法二是相同的;
2、如果p0<p1,那么我们需要再将nums[i]与nums[p1]进行交换,其中i是当前遍历到的位置,在进行了第一次交换后,nums[i]的值为1,我们需要将这个1放到【头部】的末端。在最后,无论是否有p0<p1,我们需要将p0和p1均向后移动一个位置,而不仅将p0向后移动一个位置。如下为实现代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p1 = 0;
for (int i = 0; i < n; ++i) {
//如果当前i对应的值是1,则将其与p1进行交换,然后p1进一位
if (nums[i] == 1) {
swap(nums[i], nums[p1]);
++p1;
//如果当前i对应的值是0,则将其与p0进行交换,
//如果此时p0小于p1,则还需要将现有的i与p1再进行交换,然后p0和p1位置均进一位。
} else if (nums[i] == 0) {
swap(nums[i], nums[p0]);
if (p0 < p1) {
swap(nums[i], nums[p1]);
}
++p0;
++p1;
}
}
}
};
-
时间复杂度:O(n),其中 n 是数组 nums 的长度。空间复杂度:O(1)。
解法四(双指针v2):
我们也可以考虑使用指针 p0 来交换 0,p2 来交换 2。此时,p0 的初始值仍然为 0,而 p2 的初始值为 n−1。在遍历的过程中,我们需要找出所有的 0 交换至数组的头部,并且找出所有的 2 交换至数组的尾部。
由于此时其中一个指针 p2 是从右向左移动的,因此当我们在从左向右遍历整个数组时,如果遍历到的位置超过了 p2,那么就可以直接停止遍历了。具体地,我们从左向右遍历整个数组,设当前遍历到的位置为 i,对应的元素为 nums[i];
1、如果找到了 0,那么与前面两种方法类似,将其与 nums[p0] 进行交换,并将 p0 向后移动一个位置;
2、当我们找到 2 时,我们需要不断地将其与 nums[p2] 进行交换,直到新的 nums[i] 不为 2。此时,如果 nums[i] 为 0,那么对应着第一种情况;如果 nums[i] 为 1,那么就不需要进行任何后续的操作。
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p2 = n - 1;
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {
swap(nums[i], nums[p2]);
--p2;
}
if (nums[i] == 0) {
swap(nums[i], nums[p0]);
++p0;
}
}
}
};
-
时间复杂度:O(n),其中 n 是数组 nums 的长度。空间复杂度:O(1)。
笔者小记:
1、采用指针思路时,指针可以作为满足特定条件后再移动,而不需要每次循环遍历就需要移动,即for循环遍历是,int i是与p0、p1或p2独立分开的。p0、p1或p2只是记录元素【头部】的一个标记点。
2、swap(nums[i], nums[j])函数可以实现两个数索引位置对应数组值的交换,并不会改变i和j的索引位置