LeetCode 之 颜色分类

本题可以用多种方法进行求解。如果将其看作排序问题,排序 0 、1、2,那么可以使用排序算法进行求解。如果将其看成原地调换数组元素位置,那么可以使用指针法进行求解。

知识点: 快速排序、快慢指针、循环不变量。
在这里插入图片描述

1. 快速排序

1.1 思路

快速排序采用了分治的思想,首先找出一个主元,我们一般取数组的第一个元素作为主元。然后调整数组元素的位置,使得主元左边都是小于主元的元素主元右边都是大于主元的元素。之后再分别对主元左边的元素和主元右边的元素进行排序,直至所有元素都是有序的(至少有两个元素才可以进行排序)。

1.2 实现

我们使用递归来实现快速排序,定义快速排序函数 void quicksort(vector<int>& nums, int left, int right)
递归的终止条件:待排区间至少有两个元素。因为一个元素本身就是有序的。
递归函数的作用:对当前区间元素进行排序。对主元左边的元素进行排序,对主元右边的元素进行排序。
递归的参数和返回值:除了要排序的数组,还需传入区间的左右边界。

首先需要将主元放到正确的位置上,即主元左边的元素都小于主元,主元右边的元素都大于主元。并返回主元的位置索引,用于划分递归的区间。
这里定义放置主元的函数 int partition(vector<int>& nums, int left, int right)
当 left == right 时,说明找到了主元的位置,故把主元放到 left (right)位置上,并返回索引 left (right)。

class Solution {
public:
    void sortColors(vector<int>& nums) {
        quicksort(nums, 0, nums.size() - 1);
    }
    void quicksort(vector<int>& nums, int left, int right)
    {
        if(left < right)	//终止条件
        {
            int pivot = partition(nums, left, right);
            quicksort(nums, left, pivot - 1);	
            quicksort(nums, pivot + 1, right);
        }

    }

    int partition(vector<int>& nums, int left, int right)
    {
    //将主元的值存到变量 pivot 中
        int pivot = nums[left];
        while(left < right)
        {
        // 从右边开始遍历,再次判断 left < right 防止越界
            while(left < right && nums[right] >= pivot)
            {
                right--;
            }
            // 将主元右边小于主元的元素放到主元之前的空位上
            nums[left] = nums[right];
            // 从左边开始遍历,再次判断 left < right 防止越界
            // 当 left == right 是说明循环要结束了(待排区间只有一个元素了)
            // 如果不进行再次判断,当满足 nums[left] == pivot 时,即 left == right 时
            // right--,继续左移会超出数组左边界
            // 例如:主元为 5 ,整个数组为 [5, 6, 7, 8, 9], 
            // left 最终会在数组第一个元素处与 right 重合,再向左移就会超出数组左边界
            while(left < right && nums[left] <= pivot)
            {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
};

2. 指针法

题目要求数字 0 、1、2 分别排在一起。因此可以考虑使用指针区分数字的边界。

2.1 单指针

使用单指针需要进行两次遍历。第一次将所有的0交换到数组前端(指针ptr 前),第二次将所有的1交换到数组前端(指针ptr 前),第二次交换的起始位置是所有0之后。算法的空间复杂度为O(1)。

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int ptr = 0;
        int n = nums.size();
        for(int i = 0; i < n; i++)
        {
            if(nums[i] == 0)
            {
                swap(nums[i], nums[ptr]);
                ptr++;
            }
            
        }
        for(int j = ptr; j < n; j++)
        {
            if(nums[j] == 1)
            {
                swap(nums[j], nums[ptr]);
                ptr++;
            }
            
        }
    }
};

2.2 双指针

使用双指针只需对数组进行一次遍历(循环不变量)。
循环不变量十分巧妙,具体细节可以参考力扣视频题解,我在这里简单介绍一下。
首先要给出循环不变量的定义

 all in [0,p0)
 all in (p0, i)
 all in (p1, len - 1]

在这里插入图片描述
整个数组被划分为3个部分,数字0,数字1和数字2。
定义变量 p0 和 p1 用于划分界限,p0 前全是0,变量 p1 后全是1,i 是自变量用于遍历数组。在循环过程中,我们始终要保证上述变量的定义是正确的。

起始时,所有分区应该都为空,因此有

int p0 = 0;
int i = 0;
int p1 = nums.size() - 1;

由于 i 默认为开区间,因此循环终止条件为 while(i <= p1)。因为 p1 后都为 2,无须进行循环。(p0, i) 和 (p1, len - 1] 在 i 到 p1 之间为开,因此 while 循环取等号,让 i 继续观察下一个元素。

nums[i] == 0 时,交换数字 0 到 [0,p0) 区间。为了维护 p0 前都为 0 的定义,p0 后移。同时变量 i 后移,去看下一个元素。
nums[i] == 1 时,不属于 p0 或者 p1 区间,因此 p0 和 p1 不变,自变量 i 后移,去看下一个元素。
最后是 nums[i] == 2 的部分,交换数字 2 到 (p1, len - 1] 区间。为了维护 p1 后都为 2 的定义,p1 前移。
注意:此时,无法确定交换到的元素是什么,所以 i 不变,等待下一轮的判断,即执行下一个 while 循环。

class Solution {
public:
    void sortColors(vector<int>& nums) {
        // all in [0,p0)
        // all in [p0, i)
        // all in (p1, len - 1]
        int p0 = 0;
        int i = 0;
        int p1 = nums.size() - 1;
        while(i <= p1)
        {
            if(nums[i] == 0)
            {
                swap(nums[i], nums[p0]);
                p0++;
                i++;
            }
            else if(nums[i] == 1)
            {
                i++;
            }
            else
            {
                swap(nums[i], nums[p1]);
                p1--;
            }
        }
    }
};

参考链接

  1. 颜色分类(力扣官方题解)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值