我爱学算法之—— 分治-快排
一、颜色分类
题目解析
这道题,给定一个数组nums
,其中只存在0
、1
和2
(红色、白色、蓝色);我们要将该数组分类,并按照颜色(红色、白色、蓝色)进行排序,简单来说就是将整个数组排序。
算法思路
对这道题,题目要求不使用库内置的sort
,我们就可以自己实现一种排序(快排),将数组排序后返回。(红色、白色、蓝色;也就是0、1、2
)。
这道题除了实现排序,我们还可以利用指针进行数组划分。思路类似我爱学算法之 —— 感受双指针带来的快感(上)中**移动0
**这道题。
在移动0
中,是使用双指针进行数组划分,将数组划分成两个区域(遍历过程中是三个区域)
而在这道题中,我们要将数组划分成三个区域:0
、1
、2
。我们可以使用三指针来进行数组划分。
所以,在遍历数组过程中,就会将数组划分成四个区域:
搞懂了数组划分,那就只需搞清楚遍历过程中,遇到0
、1
、2
,分别应该做什么操作即可。
遍历数组,遍历到
i
位置
nums[i] = 0
:将该数据放入[0 , left]
区域中,swap(nums[++left], nums[i++])
。(left+1
位置肯定是要么是1
,要么就是i
下标,交换left+1
和i
下标对应的元素;然后i++
,0
区域多了一个元素,left++
)nums[i] = 1
:将数据放到[left+1 , i]
区域中,i++
。nums[i] = 2
:将数据放到[right , n-1]
区域中,swap(nums[--right], nums[i])
。(right-1
位置属于区域[i , right-1]
,是未处理数据,所以交换之后不能让i++
;而区域[right , n-1]
中多了有数据,right--
)当
i
遍历到right
位置时,数组nums
中只存在三个区域(0
、1
、2
),遍历结束。初始化:
初始状态下,
left
等于-1
,right
等于n
。
代码实现
class Solution {
public:void sortColors(vector<int>& nums) {int left = -1, right = nums.size();int i = 0;while (i < right) {if (nums[i] == 0)swap(nums[++left], nums[i++]);else if (nums[i] == 1)i++;elseswap(nums[--right], nums[i]);}}
};
二、排序数组
题目解析
这道题,就是一个排序数组,题目要求不使用内置函数,时间复杂度为O(n * logn)
,很显然就是快速排序了。
算法思路
对于快速排序,这里就不详细介绍了,可以参考博主之前文章【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
这里就以挖坑法来实现快速排序:
挖坑法:
- 以最左边元素为基准值,坑位
pit
- 从右往左,找
<
基准值的,找到后将该值放到坑位,并更新坑位。- 从左往右,找
>
基准值的,找到后将该值放到坑位,并更新坑位。遍历结束,将基准值填入坑位位置;这样坑位左侧的值都是
<
基准值的,坑位右侧的值都是>
基准值的;然后对左侧区间和右侧区间分别进行快速排序。
对于挖坑法实现的快速排序(这道题是可以通过的),这里可以对其进行优化:
优化一:
对于基准值的选择,可以使用随机选择(每次随机从
[left , right]
中选取一个值作为基准值)。
优化二:
对于快速排序,右边找小,左边找大,这个过程,对于等于基准值这种情况,要么是放到右侧、要么是放在左侧;这样如果数组中所有元素都相同,这种情况快速排序的时间复杂度就是
O(n^2)
。我们可以整个过程(Partition)进行优化,将数组划分成三个区域(小于基准值、等于基准值、大于基准值)。
这样如果数组中所有元素都相同时,只需一次递归遍历。
而对于数组划分,可上述颜色分类一样,无非就是遍历过程中判断元素和基准值的大小关系。
代码实现
class Solution {
public:void qsort(vector<int>& nums, int l, int r) {if (l >= r)return;// 随机选择基准值int key = nums[l + rand() % (r - l + 1)];// partitionint left = l - 1, right = r + 1;int i = l;while (i < right) {if (nums[i] < key)swap(nums[++left], nums[i++]);else if (nums[i] == key)i++;elseswap(nums[--right], nums[i]);}// 对<key区间 >key区间进行快速排序qsort(nums, l, left);qsort(nums, right, r);}vector<int> sortArray(vector<int>& nums) {srand(time(NULL));qsort(nums, 0, nums.size() - 1);return nums;}
};
三、数组中的第K个最大元素
题目解析
这道题要求找出数组中第k
个最大的元素,典型的TOPK
问题。
算法思路
思路一:
对于这道题,找出第
k
个最大的元素,肯定是可以使用优先级队列(堆)解决的。建立小堆,遍历数组
nums
,维持堆中数据个数为k
;最终堆顶元素就是第
k
个最大的元素。时间复杂度:
O(n * logk)
思路二:
这里题目要求我们实现时间复杂度为O(n)
的算法,而使用优先级队列解决TopK
问题时间复杂度为O(n * logk)
。
快速选择排序:
在上述排序数组题目中,我们使用数组划分来代替快速排序的
partition
,将数组划分成了三部分。
< key
的区间长度为a
、= key
的区间长度为b
、> key
的区间长度为c
。
- 如果
c >=k
,那第k
个最大的元素肯定在> key
区间中。对
> key
区间继续进行快速选择排序即可。(在>key
区间中,找第k
个最大的元素)
- 如果
b + c >= k
,那第k
个最大的元素一定就是key
(区间[left+1 , right-1]
是=key
的)- 如果
b + c < k
,那第k
个最大的元素一定在< key
的区间中。对
< key
区间继续进行快速选择排序即可。(在< key
区间中,找第k-b-c
个最大的元素)
代码实现
class Solution {
public:void qsort(vector<int>& nums, int l, int r) {if (l >= r)return;int key = nums[l + rand() % (r - l + 1)];int left = l - 1, right = r + 1;int i = l;while (i < right) {if (nums[i] < key)swap(nums[++left], nums[i++]);else if (nums[i] == key)i++;elseswap(nums[--right], nums[i]);}qsort(nums, l, left);qsort(nums, right, r);}vector<int> inventoryManagement(vector<int>& stock, int cnt) {srand(time(NULL));qsort(stock, 0, stock.size() - 1);vector<int> ret(cnt, 0);for (int i = 0; i < cnt; i++)ret[i] = stock[i];return ret;}
};
四、库存管理 III
题目解析
这道题就非常简单了,给定一个数组stock
和一个整数cnt
,要求我们返回一个数组,这个数组中存储的是stock
中最小的cnt
个数。
算法思路
这道题相对就简单很多了,这道题也算是一直TopK
问题。
思路一:
解决
TopK
问题,创建大堆,维持堆中个数为cnt
,最后将堆中元素转化成数组vector
返回即可。
思路二:
将数组排序(快速排序),然后将数组前
cnt
个元素组成一个新的数组,然后返回。
代码实现
这里就实现将数组排序,然后将前cnt
个元素组成新的数组。
class Solution {
public:void qsort(vector<int>& nums, int l, int r) {if (l >= r)return;int key = nums[l + rand() % (r - l + 1)];int left = l - 1, right = r + 1;int i = l;while (i < right) {if (nums[i] < key)swap(nums[++left], nums[i++]);else if (nums[i] == key)i++;elseswap(nums[--right], nums[i]);}qsort(nums, l, left);qsort(nums, right, r);}vector<int> inventoryManagement(vector<int>& stock, int cnt) {srand(time(NULL));qsort(stock, 0, stock.size() - 1);vector<int> ret(cnt);for (int i = 0; i < cnt; i++)ret[i] = stock[i];return ret;}
};
本篇文章到这里就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws