编程题-颜色分类(中等)

题目:

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 12 分别表示红色、白色和蓝色。

    必须在不使用库内置的 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的索引位置

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值