【优选算法】分治--快速排序

目录

分治

分治的思想就是一个大问题分为小问题,再将小问题继续分解,直到分解到这个小问题能够直接得出答案位置,解决小问题后再将小问题进行合并得到大问题的答案,快速排序就是使用了分治的思想。


一、颜色分类

题目描述:

思路讲解:

本题使用快排的思想,选取1为基准元素,定义三个变量left=-1、i=0、right=nums.size(),left标记0区域的最右侧,right标记2区域的最左侧,接下来通过i来遍历数组,并对下标i对应数进行的操作进行分类讨论:

  1. nums[i]<key,swap(nums[i++],nums[++left])
  2. nums[i]==key,i++
  3. nums[i]>key,swap(nums[i],nums[--right])

编写代码:

cpp 复制代码
class Solution {
public:
    void sortColors(vector<int>& nums) {
        // 这里需要三个指针来维护四个区域
        int numsLen = nums.size();
        // [0 , left][left + 1 , cur - 1][cur , right - 1][right,numsLen-1]
        //  红色区域       白色区域          待处理区域        蓝色区域
        
        int left = -1 , right = numsLen;
        int cur = 0;  // 用于遍历数组
        
        while(cur < right)
        {
            if(nums[cur] == 0)
                swap(nums[++left],nums[cur++]);
            else if(nums[cur] == 1)
                cur++;
            else // nums[cur] == 2
                swap(nums[--right],nums[cur]);
        }
    }
};

二、排序数组

题目描述:

思路讲解:

本题需要使用快速排序的思想来解决问题,传统的快速排序只需要定义两个指针,就可以将基准元素放在正确的位置上,当单趟排序完后会将数组分为两块,定义两个指针的快排,遇到数组中全是相同元素的情况下,时间复杂度会退化到O(n^2^),这里我们定义三个指针,用"数组分三块"的思想来实现快排,遇到数组中全是相同元素的情况下,会使时间复杂度变为O(n)。

接下来讲述如何使用"数组分三块"的思想来解决问题,定义三个变量left=-1、i=0、right=nums.size(),取最左边的数/取最右边的数/三数取中的方式,选取一个基准元素,接下来通过i来遍历数组,并对下标i对应数进行的操作进行分类讨论:

  1. nums[i]<key,swap(nums[i++],nums[++left])
  2. nums[i]==key,i++
  3. nums[i]>key,swap(nums[i],nums[--right])

在i遍历的过程中,三个指针实际上是将数组分为了下图的四块,由于i和right-1指向的位置都是未被扫码区域,所以在分类讨论中的第三种情况下,我们只能判断中nums[i]>key,而nums[right-1]与key的大小关系无法得知,所以交换后还需要判断一次与key的大小关系,所以第三种情况下i不需要++。

快速排序还有一个优化方案,就是如何取基准元素,在上面讲到的取数组最右边的值/取数组最左边的值/三数取中都会在某种特殊情况下使时间复杂度退化。我们使用随机数取中的方式选择基准元素,在left和right中随机选一个数组作为基准元素,key=[rand()%(right-left+1)+left],使用这种方式选取记住元素会使快排的时间复杂度基本保持在O(n*log~2~n)。

编写代码:

cpp 复制代码
class Solution {
public:
    void _sortArray(vector<int>& nums, int left,int right)
    {
        if(left >= right)
            return;
        // [0 , l][l + 1 , cur - 1][cur , r][r + 1 , len]
        //   <key       ==key      待处理期间   >key
        int l = left - 1 , r = right + 1;
        int cur = left;

        // 取随机数为key
        int key = nums[(rand() % (right - left + 1)) + left];
        while(cur < r)
        {
            if(nums[cur] < key)
                swap(nums[cur++] , nums[++l]);
            else if(nums[cur] == key)
                ++cur;
            else // nums[cur] > key
                swap(nums[cur] , nums[--r]);
        }

        // 排序子区间
        _sortArray(nums , left , l);
        _sortArray(nums , r , right);
    }
    vector<int> sortArray(vector<int>& nums) {
        _sortArray(nums , 0 , nums.size()-1);
        return nums;
    }
};

三、数组中的第K个最大元素

题目描述:

思路讲解:

本题依旧是使用"数组分三块"的思想+随机选择基准元素的方式来解决问题。

通过第一次排序后,我们会得到下图中的三个区间,将三个区间中元素个数分别设为a、b、c个,接下来对第K个最大元素在三个不同区域的情况进行分类讨论。

  1. c>=k时,在[r,right]这个区间内有c个数组中最大的元素,第K最大元素必定在这个区间内,所以在[r,right]中继续寻找。
  2. b+c>=k时,在[l+1,r-1]这个区间内全是与key相等的数,又第K最大元素又在这个范围内,所以第K最大元素就是key,返回即可。
  3. a+b+c>=k时,[l+1,right]区间都比第K最大元素要大,在[l+1,right]区间内有b+c个元素,我们只需要在剩下的[left,l]这个区间内找到第K-b-c最大的元素即可。

重复上面的操作,重复过程中需要按分类讨论中不同情况下修改left和right的值,直到找到第K最大元素,需要注意的是,上面分类讨论中第二种情况必定在第一种情况不满足的情况,第三种情况必定是在第一和第二种情况不满足的情况。

编写代码:

cpp 复制代码
class Solution {
public:
    int _findKthLargest(vector<int>& nums, int left , int right ,int k) 
    {
        // [0 , l][l + 1 , cur - 1][cur , r][r + 1 , len]
        //   <key       ==key      待处理期间   >key
        int l = left - 1 , r = right + 1;
        int cur = left;
        // 取随机数为key
        int key = nums[(rand() % (right - left + 1)) + left];
        while(cur < r)
        {
            if(nums[cur] < key)
                swap(nums[cur++] , nums[++l]);
            else if(nums[cur] == key)
                ++cur;
            else // nums[cur] > key
                swap(nums[cur] , nums[--r]);
        }

        // 如果在大于key的区间
        // 注意r指向的是>key的边界
        if(right - r + 1 >= k) // r - left + 1 < k
            return _findKthLargest(nums , r , right , k);
        // 如果在等于key的区间,则返回key
        else if(right - l >= k)
            return key;
        // 如果在小于key的区间
        else
            return _findKthLargest(nums , left , l , k - (right - l));
    }

    int findKthLargest(vector<int>& nums, int k) {
        return _findKthLargest(nums , 0 , nums.size()-1 , k);
    }
};

四、库存管理 III

题目描述:

思路讲解:

本题的思路与上一题的思路基本一致,上一题是找到第K最大元素,本题是找到cnt个最小的元素,还是使用"数组分三块"的思想+随机选择基准元素的方式来解决问题。

通过第一次排序后,我们会得到下图中的三个区间,将三个区间中元素个数分别设为a、b、c个,本题因为返回cnt个最小元素不需要有序,实际上只需要找到第cnt最小元素,再返回包括第cnt最小元素左边所有的元素即可解决问题,接下来对第cnt最小元素在三个不同区域的情况进行分类讨论。

  1. a>=k时,在[left,l]这个区间内有a个数组中最小的元素,第cnt最小元素必定在这个区间内,所以在[r,right]中继续寻找。
  2. a+b>=k时,在[l+1,r-1]这个区间内全是与key相等的数,又第cnt最大元素又在这个范围内,所以第cnt最小元素就是key,返回[left,l]区间内所有元素和[l+1,r-1]这个区间内的cnt-(l-left+1)个元素,又[l+1,r-1]区间内所有元素都相等,我这里取[l+1,left+cnt]这段区间与左边区间连续起来。
  3. a+b+c>=k时,[left,r-1]区间都比第cnt最小元素要小,在[left,r-1]区间内有a+b个元素,我们只需要在剩下的[r+1,right]这个区间内找到第cnt-b-a最小的元素即可。

重复上面的操作,重复过程中需要按分类讨论中不同情况下修改left和right的值,直到找到cnt个最小元素,需要注意的是,上面分类讨论中第二种情况必定在第一种情况不满足的情况,第三种情况必定是在第一和第二种情况不满足的情况。我这里并没有采取返回整个数组的方式返回,而是通过函数引用传参将数组返回。

编写代码:

cpp 复制代码
class Solution {
public:
    void _inventoryManagement(vector<int>& stock, int left , int right ,int cnt ,vector<int>& ans) 
    {
        // [0 , l][l + 1 , cur - 1][cur , r][r + 1 , len]
        //   <key       ==key      待处理期间   >key
        int l = left - 1 , r = right + 1;
        int cur = left;
        // 取随机数为key
        int key = stock[(rand() % (right - left + 1)) + left];
        while(cur < r)
        {
            if(stock[cur] < key)
                swap(stock[cur++] , stock[++l]);
            else if(stock[cur] == key)
                ++cur;
            else // nums[cur] > key
                swap(stock[cur] , stock[--r]);
        }

        // 如果在小于key的区间
        if(l - left + 1 >= cnt)
            _inventoryManagement(stock , left , l , cnt , ans);
        // 如果在等于key的区间,则返回这个区间内cnt-l-left+1个key
        // 和小于key的区间内所有元素(l-left+1个元素)
        else if(r - left >= cnt)
            ans.insert(ans.end() , stock.begin() + left, stock.begin() + left + cnt);
        // 如果在大于key的区间
        // 注意r指向的是>key的边界
        else // r - left + 1 < k
        {
            ans.insert(ans.end() , stock.begin() + left, stock.begin() + left + (r - left));
            _inventoryManagement(stock , r , right , cnt - (r - left) , ans);
        }
    }

    vector<int> inventoryManagement(vector<int>& stock, int cnt) {
        if(cnt == 0)
            return {};
        vector<int> ans;
        _inventoryManagement(stock,0,stock.size()-1,cnt,ans);
        return ans;
    }
};

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
ybq195133454312 分钟前
Map和Set
算法
IT规划师6 分钟前
LeetCode题集-9 - 回文数
算法·leetcode·回文数
lihan_freak41 分钟前
力扣275.H制数II (二分法 求最大)
算法·leetcode·职场和发展
SunshineBooming1 小时前
qemu源码解析【05】qemu启动初始化流程
c++·驱动开发·源码软件
哎呦,帅小伙哦1 小时前
std::async 和 std::packaged_task
c++
魏+Mtiao15_1 小时前
短视频矩阵贴牌:打造品牌新势力的策略与实践
大数据·人工智能·算法
就爱学编程2 小时前
重生之我在异世界学编程之算法与数据结构:深入静态顺序表篇
数据结构·算法
獨枭3 小时前
MFC 应用程序语言切换
c++·mfc
是小Y啦4 小时前
leetcode 881.救生艇
算法·leetcode·职场和发展
狄加山6754 小时前
C语言(文件练习)
c语言·开发语言·算法