本文讲解分治下的快排类型的4道题,在讲解题目的同时提供AC代码,点击题目即可打开对应链接
目录
1、颜色分类
2、排序数组
3、数组中的第K个最大元素
4、库存管理 III
解法(快排思想--三指针法使数组分三块):
类比数组分两块的思想【双指针系列讲过移动零【双指针精选题目】详解8道题-CSDN博客】,这里是将数组分成三块,那么我们可以再添加一个指针,实现数组分三块。
设数组大小为 n ,定义三个指针 left, cur, right :
- left :用来标记 0 序列的末尾,因此初始化为 -1 ;
- cur :用来扫描数组,初始化为 0 ;
- right :用来标记 2 序列的起始位置,因此初始化为 n 。
在 cur 往后扫描的过程中,保证:
- [0, left] 内的元素都是 0 ;
- [left + 1, cur - 1] 内的元素都是 1 ;
- [cur, right - 1] 内的元素是待定元素;
- [right, n] 内的元素都是 2 。
算法流程:
a. 初始化 cur = 0,left = -1, right = numsSize ;
b. 当 cur < right 的时候(因为 right 表示 2 序列的左边界,因此当 cur 碰到 right 时,说明已经将所有数据扫描完毕了),一直进行下面循环:
根据 nums[cur] 的值,可以分为下面三种情况:
- ①、 nums[cur] == 0 说明这个位置的元素需要在 left + 1 的位置上,故交换 left + 1 与 cur 位置的元素,并且让 left++ (指向 0 序列的右边界),cur++ (为什么可以 ++ 呢,是因为 left + 1 位置要么是 0 ,要么是1 ,【因为lef+1位置肯定是cur指针要扫描或即将扫描的位置,即这个位置我们可认为判断过了】交换完毕之后,这个位置的值已经符合我们的要求,因此 cur++ );
- ②、nums[cur] == 1 说明这个位置应该在 left 和 cur 之间,此时无需交换,直接让 cur++ ,判断下一个元素即可;
- ③、nums[cur] == 2 说明这个位置的元素应该在 right - 1 的位置,故交换right - 1 与 cur 位置的元素,并且让 right-- (指向 2 序列的左边界),cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)
当循环结束之后:
[0, left] 表示 0 序列;
[left + 1, right - 1] 表示 1 序列;
[right, numsSize - 1] 表示 2 序列。举例分析:
class Solution {
public:
void sortColors(vector& nums) {
int n = nums.size();
int left = -1, cur = 0, right = n;
while (cur < right)
{
if (nums[cur] == 0) swap(nums[++left], nums[cur++]);
else if (nums[cur] == 1) cur++;
else swap(nums[--right], nums[cur]);
}
}
};
数组划分为两块的快排存在的问题
当待排序序列已经有序下或当待排序序列中存在大量重复元素,快速排序的划分操作可能会导致极度不平衡的划分,例如每次选择的基准值都是序列中的最小或最大值或者每次选择的基准值都是序列中的最小或最大重复元素,这样划分的效率很差,递归执行的次数也达到n-1,时间复杂度:O(N*N),但当数组中元素重复时我们可利用数组划分为三块的快排来优化
数组分为三块的快排
优化:
class Solution {
public:
vector sortArray(vector& nums)
{
srand(time(NULL));//种下随机数种子
qsort(nums, 0, nums.size() - 1);
return nums;
}
//快排
void qsort(vector& nums, int l, int r)
{
if (l >= r) return;//区间只有一个元素或不存在时为递归出口
//数组分为三块
int key = getKey(nums, l, r);//获取[l,r]中的一个随机数作为基准值
//快排最核心部分:划分
int i = l, left = l - 1, right = r + 1;
while (i < right)
{
if (nums[i] < key) swap(nums[++left], nums[i++]);
else if (nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
//继续递归左区间和右区间
//[l, left],[left + 1, right - 1], [right, r]
qsort(nums, l, left);
qsort(nums, right, r);
}
int getKey(vector& nums, int left, int right)
{
int r = rand();//获得随机数
return nums[r % (right - left + 1) + left];//获取[left,right]内的一个随机数
}
};
本题为典型的topk问题,我之前文章有讲过用堆解决这种问题
解题思路:
在快排中,把数组「分成三块」: [l, left] [left + 1, right - 1][right, r]
我们可以通过计算每一个区间内元素的「个数」,进而推断出我们要找的元素是在「哪一个区间」里面,最后直接去「相应的区间」去寻找最终结果即可。
下面的a,b,c表示对应
key各区间中的元素个数
class Solution {
public:
int findKthLargest(vector& nums, int k)
{
srand(time (NULL));//设置随机数种子
return qsort(nums, 0, nums.size() - 1, k);
}
//快排:划分为三部分
int qsort(vector& nums, int l, int r, int k)
{
if (l == r) return nums[l];//qsort中的判断条件会保证区间一定存在,故l>r的情况可以不考虑,考虑也不会错
//获取[l,r]范围内随机一个基准值
int key = getRandom(nums, l, r);
//快排核心:划分
int i = l, left = l - 1, right = r + 1;
while (i < right)
{
if (nums[i] < key) swap(nums[++left], nums[i++]);
else if (nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
//递归:求出第k大元素
int c = r - right + 1, b = right - left - 1; //b:right - 1 - (left + 1) + 1
if (c >= k) return qsort(nums, right, r, k);
else if (b + c >= k) return key;
else return qsort(nums, l, left, k - b - c);
}
//获取随机基准值
int getRandom(vector& nums, int left, int right)
{
return nums[rand() % (right - left + 1) + left];
}
};
解法一、排序:O(N*logN)
解法二、堆:O(N*logK)
解法三、快速选择算法
a,b,c为各个区间内的元素个数
class Solution {
public:
vector inventoryManagement(vector& nums, int k)
{ //为方便书写nums[]代表stock[],k代表cnt
//本题快速选择算法是把前k小的元素丢到最前面,并不是为了排序
srand(time(NULL));
qsort(nums, 0, nums.size() - 1, k);
return {nums.begin(), nums.begin() + k};
}
//快排
void qsort(vector& nums, int l, int r, int k)
{
if (l >= r) return;
int key = getRandom(nums, l, r);
int i = l, left = l - 1, right = r + 1;
while (i < right)
{
if (nums[i] < key) swap(nums[++left],nums[i++]);
else if (nums[i] == key) i++;
else swap(nums[--right], nums[i]);
}
//划分
int a = left - l + 1, b = right - left - 1;
if (a >= k) qsort(nums, l, left, k);//去或>=均可
else if (a + b >= k) return;//说明前k个元素已找到
else qsort(nums, right, r, k - a - b);
}
int getRandom(vector& nums, int left, int right)
{
return nums[rand() % (right - left + 1) + left];
}
};