目录
一、颜色分类
1. 题目链接:75. 颜色分类
2. 题目描述:
3. 解法(快排思想 - 三指针法使数组分三块)
🌴算法思路:
🌴算法流程:
🌴算法代码:
二、快速排序
1. 题目链接:912. 排序数组
2. 题目描述:
3. 解法(数组分三块思想 + 随机选择基准元素的快速排序):
🌴算法思路:
🌴算法流程:
🌴算法代码:
三、快速选择算法
1. 题目链接:215. 数组中的第K个最大元素
2. 题目描述:
3. 解法
🌴算法思路:
🌴算法代码:
四、最小的K个数
1. 题目链接:LCR 159. 库存管理 III
2. 题目描述:
3. 解法
🌴算法思路:
🌴算法代码:
一、颜色分类
1. 题目链接:75. 颜色分类
2. 题目描述:
给定一个包含红色、白色和蓝色、共
n
个元素的数组nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数
0
、1
和2
分别表示红色、白色和蓝色。必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]示例 2:
输入:nums = [2,0,1] 输出:[0,1,2]提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
3. 解法(快排思想 - 三指针法使数组分三块)
🌴算法思路:
类比数组分两块的算法思想,这里是将数组分成三块,那么我们可以再添加⼀个指针,实现数组分三块。
设数组大小为 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 ,要么是 cur ,交换完毕之后,这个位置的值已经符合我们的要求,因此 cur++ );
- nums[cur] == 1 ;说明这个位置应该在 left 和 cur 之间,此时无需交换,直接让 cur++ ,判断下一个元素即可;
- nums[cur] == 2 ;说明这个位置的元素应该在 right - 1 的位置,因此交换 right - 1 与 cur 位置的元素,并且让 right-- (指向 2 序列的左边界), cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)
c. 当循环结束之后:
- [0, left] 表示 0 序列;
- [left + 1, right - 1] 表示 1 序列;
- [right, numsSize - 1] 表示 2 序列。
🌴算法代码:
class Solution
{
public:
void sortColors(vector<int>& nums)
{
int n = nums.size();
int left = -1, right = n, i = 0;
while (i < right)
{
if(nums[i] == 0) swap(nums[++left], nums[i++]);
else if(nums[i] == 1) i++;
else swap(nums[--right], nums[i]);
}
}
};
二、快速排序
1. 题目链接:912. 排序数组
2. 题目描述:
给你一个整数数组
nums
,请你将该数组升序排列。你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为
O(nlog(n))
,并且空间复杂度尽可能小。示例 1:
输入:nums = [5,2,3,1] 输出:[1,2,3,5]示例 2:
输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]提示:
1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104
3. 解法(数组分三块思想 + 随机选择基准元素的快速排序):
🌴算法思路:
由快速排序的思想可以知道,快排最核心的⼀步就是 Partition (分割数据):将数据按照一个标准,分成左右两部分。
如果我们使用荷兰国旗问题的思想,将数组划分为 左 中 右 三部分:左边是比基准元素小的数据,中间是与基准元素相同的数据,右边是比基准元素大的数据。然后再去递归的排序左边部分和右边部分即可(可以舍去大量的中间部分)。
在处理数据量有很多重复的情况下,效率会大大提升。
🌴算法流程:
随机选择基准算法流程:
函数设计:int randomKey(vector<int>& nums, int left, int right)
- 在主函数那里种⼀颗随机数种子;
- 在随机选择基准函数这里生成⼀个随机数;
- 由于我们要随机产生⼀个基准,因此可以将随机数转换成随机下标:让随机数 % 上区间大小,然后加上区间的左边界即可。
快速排序算法主要流程:
a. 定义递归出口;;
b. 利用随机选择基准函数生成⼀个基准元素;
c. 利用荷兰国旗思想将数组划分成三个区域;
d. 递归处理左边区域和右边区域。
🌴算法代码:
class Solution
{
public:
vector<int> sortArray(vector<int>& nums)
{
srand(time(NULL));//种下一个随机数种子
qsort(nums, 0, nums.size() - 1);
return nums;
}
// 快排
void qsort(vector<int>& nums, int l, int r)
{
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]);
}
// [l, left] [left+1, right-1] [right, r]
qsort(nums, l, left);
qsort(nums, right, r);
}
int getRandom(vector<int>& nums, int left, int right)
{
int r = rand();
return nums[r % (right - left + 1) + left];
}
};
三、快速选择算法
1. 题目链接:215. 数组中的第K个最大元素
2. 题目描述:
给定整数数组
nums
和整数k
,请返回数组中第k
个最大的元素。请注意,你需要找的是数组排序后的第
k
个最大的元素,而不是第k
个不同的元素。你必须设计并实现时间复杂度为
O(n)
的算法解决此问题。示例 1:
输入:[3,2,1,5,6,4], k = 2 输出: 5示例 2:
输入:[3,2,3,1,2,4,5,5,6],k = 4 输出: 4提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
3. 解法
🌴算法思路:
在快排中,当我们把数组「分成三块」之后: [l, left] [left + 1, right - 1][right, r] ,我们可以通过计算每一个区间内元素的「个数」,进而推断出我们要找的元素是在「哪一个区间」里面。那么我们就可以直接去「相应的区间」寻找最终结果啦。
🌴算法代码:
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
srand(time(NULL));
return qsort(nums, 0, nums.size() - 1, k);
}
int qsort(vector<int>& nums, int l, int r, int k)
{
if(l == r) return nums[l];
// 1.随机选择基准元素
int key = getRandom(nums, l, r);
// 2.根据基准元素将数组分三块
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]);
}
// 3.分情况讨论
int c = r - right + 1, b = right - left - 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<int>& nums, int left, int right)
{
return nums[rand() % (right - left + 1) + left];
}
};
四、最小的K个数
1. 题目链接:LCR 159. 库存管理 III
2. 题目描述:
仓库管理员以数组
stock
形式记录商品库存表,其中stock[i]
表示对应商品库存余量。请返回库存余量最少的cnt
个商品余量,返回 顺序不限。示例 1:
输入:stock = [2,5,7,4], cnt = 1 输出:[2]示例 2:
输入:stock = [0,2,3,6], cnt = 2 输出:[0,2] 或 [2,0]提示:
0 <= cnt <= stock.length <= 10000
0 <= stock[i] <= 10000
3. 解法
🌴算法思路:
在快排中,当我们把数组「分成三块」之后: [l, left] [left + 1, right - 1][right, r] ,我们可以通过计算每⼀个区间内元素的「个数」,进而推断出最小的 k 个数在哪些区间里面。那么我们可以直接去「相应的区间」继续划分数组即可。
🌴算法代码:
class Solution
{
public:
vector<int> inventoryManagement(vector<int>& stock, int cnt)
{
srand(time(NULL));
qsort(stock, 0, stock.size() - 1, cnt);
return {stock.begin(), stock.begin() + cnt};
}
void qsort(vector<int>& stock, int l, int r, int cnt)
{
if(l >= r) return;
//选择基准元素 + 数组分三块
int key = keyRandom(stock, l, r);
int i = l, left = l - 1, right = r + 1;
while(i < right)
{
if(stock[i] < key) swap(stock[++left], stock[i++]);
else if(stock[i] == key) i++;
else swap(stock[--right], stock[i]);
}
// 分情况讨论
// [l, left] [left + 1, right - 1] [right, r]
int a = left - l + 1, b = right - left - 1;
if(a > cnt) return qsort(stock, l, left, cnt);
else if(a + b >= cnt) return;
else return qsort(stock, right, r, cnt - a - b);
}
int keyRandom(vector<int>& stock, int l, int r)
{
return stock[rand() % (r - l + 1) + l];
}
};