力扣详解(75颜色分类)

本文详细探讨了如何通过三指针、双指针(包括一种左闭右开和一种首尾双向)和单指针的方法解决LeetCode 75题的颜色排序问题。讲解了每种解题思路的关键步骤,并揭示了常见陷阱。同时,文中提到了几种排序算法的简化版本,如冒泡排序、选择排序和插入排序,但未涉及归并排序、计数排序和快速排序的错误代码。

一、题目

略,请见力扣 75 题。

二、题解

(一)为了解题而解题

(1)三指针一次循环
可以看作有三个区间:全 0 区间、全 1 区间、全 2 区间。
定义三个变量指向(标记)三个区间末尾的下一个位置,看图:
在这里插入图片描述
为什么这么安排,先看代码再看几个主要步骤:

class Solution {
   
   
public:
    void sortColors(vector<int>& nums) {
   
   
        //0,1,2的指针
        int nums0 = 0,nums1 = 0,nums2 = 0;
        //数组大小
        int n = nums.size();
        //遍历计数器
        int i = 0;
        while(i <= (n - 1))
        {
   
   
            if(nums[i] == 0)
            {
   
   
                nums[nums2++] = 2;
                nums[nums1++] = 1;
                nums[nums0++] = 0;
            }
            else if(nums[i] == 1)
            {
   
   
                nums[nums2++] = 2;
                nums[nums1++] = 1;
            }
            else
            {
   
   
                nums[nums2++] = 2;
            }
            ++i;
        }
    }
};

①假设刚开始有如下数组:
在这里插入图片描述
②第一次 while 循环,执行 else 语句:
在这里插入图片描述
③第二次 while 循环,执行 else if 语句:
在这里插入图片描述
在这里插入图片描述
④第三次 while 循环,执行 else 语句:
在这里插入图片描述
⑤第四次 while 循环,执行 if 语句:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到这里就基本能了解了。
(2)双指针一次循环(第一种)
根据题目可以知道,最终排好后的数组是 0 全部在数组首部,1 全部在数组中间,2全部在数组尾部。
所以定义一个遍历计数器,再定义两个变量 left 、 right 分别指向数组首尾,计数器往后走,当计数器所在位置元素为 0 则将该数与 left 当前所在位置的元素交换,然后 left 往后走,同理如果计数器所在位置是 2 则将该数与 right 当前所在位置元素交换,然后 right 往前走,直到 i > right 结束循环,其中 0 和 2 排好后,1 自然排到正确的位置。
先看代码再看几个主要步骤:

class Solution {
   
   
public:
    void sortColors(vector<int>& nums) {
   
   
        //数组长度
        int n = nums.size();
        //首尾双指针
        int left = 0,right = n - 1;
        //遍历计数器
        int i = 0;
        while(i <= right)
        {
   
   
            if(nums[i] == 0)
            {
   
   
                swap(nums[left++],nums[i++]);//交换后都往后走
            }
            else if(nums[i] == 2)
            {
   
   
                swap(nums[right--],nums[i]);
            }
            else  ++i;//为 1 直接下一次排序
        }
    }
private:
    void swap(int &a,int &b)
    {
   
   
        int tmp = a;
        a = b;
        b = tmp;
    }
};

有几个细节地方要注意一下,也是我绊倒的地方:

问:循环条件不能是 left < right?
答:有一种情况,比如 10 个数的数组中,只有 2 个 0,5 个 1,3 个 2,头脑快的可以直接想到 left 和 right 永远不会牵手,所有的 0 和 2 分别交换到数组首部和尾部后,left = 2,right = 6,头脑没那么快的看图:

①刚开始:
在这里插入图片描述
②中间省略一点步骤,直接到最后一个 2 被交换到数组尾部后的情况,此时 right = 6,可见所有 2 都被交换到了数组尾部,所以 right 不会再移动:
在这里插入图片描述
③中间省略一点步骤,直接到最后一个 0 被交换到数组首部后的情况,可见所有 0 都已经交换到数组首部,所以 left 不会再移动,而 left 依旧小于 right,i 也就会继续往后走,但是现在数组已经有序,所以就会出现错误:
在这里插入图片描述
④如其中一个错误:
在这里插入图片描述
此时 nums[i] = 2,会执行 else if 语句,最终导致错误
在这里插入图片描述

问:else 语句可不可以放到最前面?
答:不可以,如果你当前 i 的位置的后面连续几个数刚好是 1 ,0,2,则会出错。看图:

①刚开始:
在这里插入图片描述
②第一次循环后:
在这里插入图片描述
③第二次循环就满足了1,0,2 的情况,则三个语句都会执行:
第一个 if 条件满足
在这里插入图片描述
并且 else if 条件满足
在这里插入图片描述
离谱的是 else 也满足了,所以出错了
在这里插入图片描述
④第三次循环条件不成立,跳出,得出错误结果

问:循环条件 i <= right 不是多此一举吗?
答:因为 right 指向的是全 2 区间的前一个位置,不在全 2 区间里,也就是说 right 指向的位置的数是没有排序的,所以 i 要走到和 right 同一个位置才算完整。

①刚开始:
在这里插入图片描述
②第一次循环:
在这里插入图片描述
③第二次循环,此时 i = right 不满足 i < right,循环结束,结果错误。

问:else if 语句中 swap 中的 nums[i] 不可以写成 nums[i++] 吗?
答:不可以,因为你不能保证的当前交换的 right 位置的值就是 0 或者 1,万一是 2,而你 i++,相当于下一次循环就是后面一个位置的数了,从而出错。看图:

①刚开始:
在这里插入图片描述
②中间省略一点,直接到第二次循环:
在这里插入图片描述
执行 else if 语句,交换后
在这里插入图片描述
③第三次循环,也就是最后一次循环,得到的结果显然不正确:
在这里插入图片描述
(3)双指针一次循环(第二种)
同样,定义两个标记变量(指针)和一个遍历计数器,不同于第一种的是,两个指针不是一首一尾,而是都从数组首部开始往后走,分别标记全 0 区间和全 1 区间末尾的后一位置。不明白?
先看代码再看几个关键图:

class Solution {
   
   
public:
    void sortColors(vector<int>& nums) {
   
   
        //双定位变量
        int p0 = 0,p1 = 0;
        //遍历计数器
        int i;
        for(i = 0;i < nums.size()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值