leetcode - 分治-三路划分快速排序总结

系列文章目录

  1. leetcode - 双指针问题_leetcode双指针题目-CSDN博客
  2. leetcode - 滑动窗口问题集_leetcode 滑动窗口-CSDN博客
  3. 高效掌握二分查找:从基础到进阶-CSDN博客
  4. leetcode - 前缀和_前缀和的题目-CSDN博客
  5. 动态规划 - 斐波那契数列模型-CSDN博客
  6. 位运算 #常见位运算总结 #题解-CSDN博客
  7. 模拟 - #介绍 #题解-CSDN博客

目录

系列文章目录

前言

一、分治思想

二、快排

三、题解

1、题1 颜色分类:

解法:三指针

参考代码:

2、题2 排序数组 :

解法一:快速排序

参考代码:

3、题3 数组中的第K个最大元素:

解法:

参考代码:

4、题4 库存管理 III:

解法一:排序

解法二:堆排序

解法三:快速排序算法

参考代码:

总结


前言

路漫漫其修远兮,吾将上下而求索;


大家可以先自己尝试做一下~

75. 颜色分类 - 力扣(LeetCode)

912. 排序数组 - 力扣(LeetCode)

215. 数组中的第K个最大元素 - 力扣(LeetCode)

LCR 159. 库存管理 III - 力扣(LeetCode)


一、分治思想

分治,即分而治之

将大问题拆解为相同或者相似的子问题,然后在这个子问题额基础上继续进行划分,直到划分到某一个位置为止(该子问题可以快速解决);

其中,排序之中,快排、归并排序就是典型的分治思想;

二、快排

想要更加详细地了解快排可以参考:数据结构与算法 - 快速排序 #hoare版本 #挖坑法 #lomuto前后指针 #三数取中 #三路划分 #introsort自省排序_lomuto法-CSDN博客

当待排序列中有大量重复数据的时候,三路划分的思想类似于hoare 版本和lomuto 前后指针的结合;其核心思想是将数组中的数据划分为三段:小于基准值的数据、等于基准值的数据、大于基准值的数据,故而叫做三路划分算法;

数据结构与算法 - 快速排序 #hoare版本 #挖坑法 #lomuto前后指针 #三数取中 #三路划分 #introsort自省排序_lomuto法-CSDN博客

当待排序列中有大量重复数据的时候,三路划分的思想类似于hoare 版本和lomuto 前后指针的结合;其核心思想是将数组中的数据划分为三段:小于基准值的数据、等于基准值的数据、大于基准值的数据,故而叫做三路划分算法

使用三指针:left 、cur、 right

让left 指向等于key 区间的左下标,让right 指向等于 key区间的右下标;然后让cur 遍历整个数组;

三路划分版快排代码如下:

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>

using namespace std;

//三路划分快排
void QuickSort(vector<int>& a, int left, int right)
{
	//确保范围合法
	if (left >= right) return;

	//提前保存一下范围
	int begin = left, end = right;

	//随机值取基准值
	int keyi = left + rand() % (right - left + 1);
	swap(a[keyi], a[left]);
	keyi = left;

	int key = a[left];//基准值
	int cur = left + 1;
	while(cur <= right)
	{
		if (a[cur] < key)
		{
			swap(a[left], a[cur]);
			cur++;
			left++;
		}
		else if (a[cur] > key)
		{
			swap(a[right], a[cur]);
			//因为不知道a[right] 中数据地大小,所以right--,而 cur 不动
			right--;
		}
		else//与 key 相等
		{
			cur++;
		}
	}
	//[begin,left-1] 小于key 的区间
	//[left , right] 等于key 的区间
	//[right ,end] 大于key 的区间
	//继续递归
	QuickSort(a, begin, left - 1);
	QuickSort(a, right+1, end);
}

int main()
{
	//设置随机种子
	srand((unsigned int)time(NULL));
	vector<int> nums = { 10,2,90,8,76,23,12,11,5,3,2 };
	QuickSort(nums, 0, nums.size() - 1);
	for (auto e : nums)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

三、题解

1、题1 颜色分类:

75. 颜色分类 - 力扣(LeetCode)

本题的核心在于将数组分为三块;

解法:三指针

其实就是三路划分版本的快排;

设置三个变量: left 、cur、right

  • cur : 遍历数组中的数据
  • left : 标记 1 区域的最左侧
  • right :标记 1 区域的最右侧

而当 cur > right 的时候代表cur 遍历完了所有的数据,循环结束;故循环条件为 cur<=right;

那么区间 [0,left-1] 中全为0,区间 [left,right] 中全为1, 区间 [right+1,n-1] 中全为2;

参考代码:
    void sortColors(vector<int>& nums) 
    {
        //三路划分
        int left = 0, right = nums.size()-1, cur = 0;
        int key = 1;
        while(cur <= right)
        {
            if(nums[cur] < key) swap(nums[left++], nums[cur++]);
            else if(nums[cur] > key) swap(nums[right--] , nums[cur]);
            else cur++;
        }
    }

2、题2 排序数组 :

912. 排序数组 - 力扣(LeetCode)

解法一:快速排序

其实使用排序算法就可以解决,而三路划分版本的快速排序当数据大量重复时进行的优化;

我们再来过一遍三路划分的快速排序;

首先第一步是寻找一个基准值 key , 我们目标是将数组nums 划分为三个部分,一部分小于 key , 一部分等于 key , 另一部分大于key ;这样划分之后,便不用管等于key 的区域,因为中间的这部分区域中的数据一定是到了最终合适的位置上;

三指针+分类讨论:

三指针: left cur  right 

  • cur: 遍历数组
  • left : 指向等于 key 区间的最左区间
  • right : 指向等于 key 区间的最右区间

取基准值也有讲究,我们可以采用三数取中的方式,也可以是采用取随机值的方式;此处采用取随机值来取随机值;

参考代码:

    void QuickSort(vector<int>& nums, int left , int right)
    {
        //确保处理的空间合理
        if(left>=right) return;

        //保存范围
        int begin = left , end = right;
        int cur = left +1;
        
        //随机数取基准值
        int keyi = left + rand()%(right-left+1);
        swap(nums[left],nums[keyi]);

        int key = nums[left];
        left++;

        while(cur <= right)
        {
            if(nums[cur] < key)  swap(nums[cur++] , nums[left++]);
            else if(nums[cur] > key) swap(nums[cur] , nums[right--]);
            else cur++;
        }

        //begin , left-1 left,right right+1, end
        QuickSort(nums,begin , left-1);
        QuickSort(nums, right+1,end);
    }
    vector<int> sortArray(vector<int>& nums) 
    {
        //设置随机数种子
        srand((unsigned int)time(NULL));
        QuickSort(nums, 0, nums.size()-1);
        return nums;
    }

3、题3 数组中的第K个最大元素:

215. 数组中的第K个最大元素 - 力扣(LeetCode)

解法:

首要目标将数组中的数据进行排序就可以了;

第 k最大的元素,可以使用堆排序,也可以使用快排;

使用三路划分的快排,如果能确定第 k 大的数据落在哪一个区域,那么另外两个区域就不用考虑了;仅需在该区域中找第 k 大的数据即可;那我们可以借助于数据个数进行划分,专门去某一个区间查找数据就可以了;

需要注意的是,当 c>=k 就意味着,k 位于区域[right+1,r] 之中,只需要对这个区域中的数据进行排序即可,排完序之后,第 k 大的数据就是目标所求的数据;当 b+c >= k 的时候,条件 c>=k 放在前面就说明,k 并不大于等于c ,而是处于区间[left,right] 之中,而 [left,right] 中的数据全是等于基准值的,此时只要返回key 即可;而至于当上述两个情况均不满足的时候,就说明 k 小于等于 a ,只要去 [l,left-1] 中进行查找,但是由于数据排的是升序,那么就得找到区间[l,left-1] 排完序之后第 (k-b-c )大的数据才行;

参考代码:
    int QSort(vector<int>&nums, int l , int r, int k)
    {
        if(l == r) return nums[l];

        //随机数取基准值
        int keyi = l + rand()%(r-l+1);
        swap(nums[keyi] , nums[l]);
        int key = nums[l];

        //根据基准值元素将数据划分为三块
        int left = l, right = r, cur = l;
        while(cur <= right)
        {
            if(nums[cur] < key) swap(nums[left++] , nums[cur++]);
            else if(nums[cur] > key) swap(nums[cur],nums[right--]);
            else cur++;
        }
        //分情况讨论下一次去那个区间
        //l,left-1 left,right right+1,r
        int a = left - 1 - l +1 , b = right - left +1 , c = r - (right+1) +1;
        if( c >= k) return QSort(nums, right+1, r , k);
        else if( b+ c >= k) return key;
        else return QSort(nums, l,left-1, k-b-c);
    }
    int findKthLargest(vector<int>& nums, int k) 
    {
        //三路划分版本的快排
        srand((unsigned int)time(NULL));//设置随机数种子
        return QSort(nums,0,nums.size()-1 , k);
    }

4、题4 库存管理 III:

LCR 159. 库存管理 III - 力扣(LeetCode)

解法一:排序

调用sort 然后取得最小的前cnt 的个数;这种解法的时间复杂度为 O(NlogN);

解法二:堆排序

topK问题,直接使用堆排序;该解法的时间复杂度为O(NlogK);

解法三:快速排序算法

即三路划分版的快速排序算法;与上一题的思路相似,这种解法最优,时间复杂度为:O(N)

参考代码:
    void QSort(vector<int>&nums , int l, int r , int k)
    {
        if( l>= r) return;

        //随机数取基准值
        int keyi = l + rand()%(r-l+1);
        swap(nums[l] , nums[keyi]);
        int key = nums[l];

        //三路划分 - 三指针
        int cur = l+1 , left = l , right = r;
        while(cur <= right)
        {
            if(nums[cur] < key) swap(nums[cur++] , nums[left++]);
            else if(nums[cur] > key) swap(nums[cur] , nums[right--]);
            else cur++;
        }

        //继续进行排序的区间- 分情况讨论
        //l,left-1 left,right right+1,r
        int a = left -1 -l+1, b = right-left +1, c = r - right;
        if(k<=a) QSort(nums, l , left-1,k);
        else if( k<= a +b) return;
        else QSort(nums, right+1, r , k-a-b); 
    }
    vector<int> inventoryManagement(vector<int>& stock, int cnt) 
    {
        //设置随机数种子
        srand((unsigned int)time(NULL));
        QSort(stock, 0, stock.size()-1,cnt);
        return {stock.begin(), stock.begin()+cnt};
    }

总结

三路划分算法:

当待排序列中有大量重复数据的时候,三路划分的思想类似于hoare 版本和lomuto 前后指针的结合;其核心思想是将数组中的数据划分为三段:小于基准值的数据、等于基准值的数据、大于基准值的数据,故而叫做三路划分算法;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值