目录
- 分治
- 一、[颜色分类](https://leetcode.cn/problems/sort-colors/description/)
- 二、[排序数组](https://leetcode.cn/problems/sort-an-array/description/)
- 三、[数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/description/)
- [四、[库存管理 III](https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/description/)](#四、库存管理 III)
- 结尾
分治
分治的思想就是一个大问题分为小问题,再将小问题继续分解,直到分解到这个小问题能够直接得出答案位置,解决小问题后再将小问题进行合并得到大问题的答案,快速排序就是使用了分治的思想。
一、颜色分类
题目描述:
思路讲解:
本题使用快排的思想,选取1为基准元素,定义三个变量left=-1、i=0、right=nums.size()
,left标记0区域的最右侧,right标记2区域的最左侧,接下来通过i来遍历数组,并对下标i对应数进行的操作进行分类讨论:
nums[i]<key,swap(nums[i++],nums[++left])
nums[i]==key,i++
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对应数进行的操作进行分类讨论:
nums[i]<key,swap(nums[i++],nums[++left])
nums[i]==key,i++
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个最大元素在三个不同区域的情况进行分类讨论。
- c>=k时,在[r,right]这个区间内有c个数组中最大的元素,第K最大元素必定在这个区间内,所以在[r,right]中继续寻找。
- b+c>=k时,在[l+1,r-1]这个区间内全是与key相等的数,又第K最大元素又在这个范围内,所以第K最大元素就是key,返回即可。
- 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最小元素在三个不同区域的情况进行分类讨论。
- a>=k时,在[left,l]这个区间内有a个数组中最小的元素,第cnt最小元素必定在这个区间内,所以在[r,right]中继续寻找。
- 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]这段区间与左边区间连续起来。
- 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;
}
};
结尾
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹