系列文章目录
- leetcode - 双指针问题_leetcode双指针题目-CSDN博客
- leetcode - 滑动窗口问题集_leetcode 滑动窗口-CSDN博客
- 高效掌握二分查找:从基础到进阶-CSDN博客
- leetcode - 前缀和_前缀和的题目-CSDN博客
- 动态规划 - 斐波那契数列模型-CSDN博客
- 位运算 #常见位运算总结 #题解-CSDN博客
- 模拟 - #介绍 #题解-CSDN博客
目录
前言
路漫漫其修远兮,吾将上下而求索;
大家可以先自己尝试做一下~
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 颜色分类:

本题的核心在于将数组分为三块;
解法:三指针
其实就是三路划分版本的快排;
设置三个变量: 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 排序数组 :

解法一:快速排序
其实使用排序算法就可以解决,而三路划分版本的快速排序是当数据大量重复时进行的优化;
我们再来过一遍三路划分的快速排序;
首先第一步是寻找一个基准值 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 前后指针的结合;其核心思想是将数组中的数据划分为三段:小于基准值的数据、等于基准值的数据、大于基准值的数据,故而叫做三路划分算法;


262

被折叠的 条评论
为什么被折叠?



