算法 | 快速排序

目录

颜色分类

题解:

 排序数组

题解:

TOP K 问题 -- 快速选择算法

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

题解:

数组中最小的 K 个数

 题解:


对于数组,一开始就要定好区间是左闭右闭、左闭右开、左开右闭还是左开右开!

颜色分类

75. 颜色分类 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/sort-colors/

题解:

用到的方法和二分查找的移动零类似。

  • 用 left 、right 和 i 指针把数组分为四部分:[ 0 , left ] 存放的是数字 0,[ left+1 , i-1 ] 存放的是数字1,[ i , right - 1 ] 是还没有遍历的数字,[ right , n-1 ] 存放的是数字 2.

  • 当 i 和 right 相遇时,整个数组也归类完毕,[ 0 , left ] 存的是数字 0[ left+1 , right-1 ] 存的是数字 1,[ right , n-1 ] 存的是数字 2!
  • 要注意 left 和 right 的初始值,left = -1,right = n,因为初始时整个数组都还没有遍历,一切的数字都还没有归类,不能直接认为下标为 0 的位置存放的就是 0,下标 n-1 的位置存放的就是 2 !
  • 要注意是 i<right,而不是 i < n,这样分类会出错!
  • 要注意和 left 位置交换时 i++和 right 位置交换时 i 不用++
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n=nums.size();
        int left=-1,right=n;
        for(int i=0;i<right;)
        {
            if(nums[i]==0)  swap(nums[++left],nums[i++]);
            else if(nums[i]==1) ++i;
            else swap(nums[--right],nums[i]);
        }
    }
};

 排序数组

912. 排序数组 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/sort-an-array/description/

题解:

快速排序:数组分三块+选key值

快速排序的思路和颜色分类类似,排序的过程类似于一个分类的过程,为了分类,我们需要选出一个 key 值作为标准。随机选择数组中的一个数作为 key 值即可

把小于 key 值的数字放到 key 的左边,等于 key 值的数字放在中间,大于 key 值得数字放到 key 的右边。如下图:

第一次分类后,[ 0 , left ] 都是比 key 小的数, [ right , n-1 ] 都是比 key 大的数,但是这两个区间里面的数还是乱序的,所以需要在这两个区间内分别选择 key 值,继续归类排序。

由于排序的思路都是类似的,只是区间有所差异,函数可以选择需要排序的区间的左端点和右端点作为参数,我们选择区间为左闭右闭。

for 循环内的代码是快速排序算法的核心,需要理解掌握!

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        srand(time(NULL));
        int n=nums.size();
        myqsort(nums,0,n-1);//左闭右闭
        return nums;
    }
    void myqsort(vector<int>& nums,int begin,int end)
    {
        if(begin>=end) return;
        int key=FindKey(nums,begin,end);
        int left=begin-1,right=end+1;//左开右开
        //[begin,left][left+1,i][i,right-1][right,end]
        //->[begin,left][left+1,right-1][right,end]
        for(int i=begin;i<right;)
        {
            if(nums[i]<key) swap(nums[++left],nums[i++]);
            else if(nums[i]==key)   i++;
            else swap(nums[--right],nums[i]);
        }
        myqsort(nums,begin,left);  myqsort(nums,right,end);
    }
    int FindKey(vector<int>& nums,int left,int right)
    {
        //随机选择 key 值
        int t=rand();
        return nums[t%(right-left+1)+left];
    }
};

TOP K 问题 -- 快速选择算法

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

215. 数组中的第K个最大元素 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/kth-largest-element-in-an-array/description/ 

题解:

由于是找出第 K 个最大的元素,其实并不需要把所有的元素都排序后返回第 K 大的元素。

在第一次分类后,[ 0 , left ] 都是比 key 值小的数(假设区间长度为 a), [ left+1 , right-1 ] 都是等于 key 值的数(假设区间长度为 b), [ right , end ] 都是比 key 值大的数(假设区间长度为 c)。

如果第 K 大的元素落在 [ right , end ] 区间,即第 K 大的元素比 key 值大,即 c >= K,那么 [ 0 , left ] 区间就没有必要排序了,只需要排序 [ right , end ] 区间,在 [ right , end ] 区间 内找到第 K 大的元素即可

如果第 K 大的元素落在 [ left+1,right-1 ] 区间,恰好等于 key 值,即 b+c >= K,那么直接返回 key 值即可,没有必要继续排序了;

如果第 K 大的元素落在 [ 0 , left ] 区间,即 K > b+c,那么 [ right , end ] 区间就没有必要排序了,只需要排序 [ 0 , left ] 区间,此时只需要在  [ 0 , left ] 中找到第 K - b - c 大的数即可

所以这道题只是在排序数组的基础上,增加下一步需要对哪个区间排序的判断而已。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(NULL));
        return _FindKthLargest(nums,0,nums.size()-1,k);//左闭右闭
    }
    int _FindKthLargest(vector<int>& nums, int begin,int end,int k)
    {
        if(begin==end)  return nums[begin];
        int key=getKey(nums,begin,end);//左闭右闭
        int left=begin-1,right=end+1;
        for(int i=begin;i<right;)
        {
            if(nums[i]<key)    swap(nums[++left],nums[i++]);
            else if(nums[i]==key)   i++;
            else swap(nums[--right],nums[i]);
        }
        if(end-right+1>=k)   return _FindKthLargest(nums,right,end,k);
        else if(end-left>=k)    return key;
        else return _FindKthLargest(nums,begin,left,k-end+left);
    }
    int getKey(vector<int>& nums,int left,int right)
    {
        return nums[rand()%(right-left+1)+left];//左闭右闭
    }
};

数组中最小的 K 个数

LCR 159. 库存管理 III - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/description/ 

 题解:

这一道题其实就是找去数组中最小的 K个数,和上一题的思路类似:

如果最小的 K 个数在 [ 0 , left ] 中,即 a > K,那么只需要对 [ 0 , left ] 区间进行排序;

如果最小的 K 个数落在  [ 0 , right-1 ] 区间内,即 a+b >= K,那么直接返回即可,不需要继续排序了;

如果不符合上面两种的情况,那么需要对  [ right , end ] 区间进行排序,找出区间内最小的 K-a-b 个数即可。

class Solution {
public:
    vector<int> inventoryManagement(vector<int>& stock, int cnt) {
        srand(time(NULL));
        _inventoryManagement(stock,0,stock.size()-1,cnt);//左闭右闭
        return {stock.begin(),stock.begin()+cnt};
    }
    void _inventoryManagement(vector<int>& stock, int begin,int end,int k)
    {
        if(begin==end)  return;
        int key=getKey(stock,begin,end);//左闭右闭
        int left=begin-1,right=end+1;
        for(int i=begin;i<right;)
        {
            if(stock[i]<key)    swap(stock[++left],stock[i++]);
            else if(stock[i]==key)  i++;
            else swap(stock[--right],stock[i]);
        }
        //[begin,left][left+1,,right-1][right,end]
        int a=left-begin+1,b=right-left-1,c=end-right+1;
        if(a>k)    _inventoryManagement(stock,begin,left,k);
        else if(a+b>=k) return;
        else    _inventoryManagement(stock,right,end,k-a-b);
    }
    int getKey(vector<int>& stock, int begin,int end)
    {
        return stock[rand()%(end-begin+1)+begin];
    } 
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值