1. 分治:快速排序
关于快速排序算法的详细介绍请移步至此博文:
1.1 库存管理 III
解题思路:
这道题是经典的TopK问题,要求返回数组中前小的数,通用解法是通过堆排序 (大根)逐个筛选出前k小的数,时间复杂度是O(Nlogk)。但是由于返回顺序不定,因此我们可以选用快速选择 算法,在筛选出符合条件的区间后,直接返回。时间复杂度为O(N)。
具体的实现方式依旧是随机选key + 数组分三块 :

cpp
class Solution {
public:
void Quick_Sort_K(vector<int>& nums, int l, int r, int k)
{
if(l >= r) return;
int key = getRandom(nums, l,r);
int left = l - 1;
int right = r + 1;
int i = l;
while(i < right)
{
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] > key) swap(nums[--right], nums[i]);
else i++;
}
int a = left - l + 1;
int b = right - left - 1;
if(k <= a) Quick_Sort_K(nums, l, left, k);
else if(k <= a + b) return;
else Quick_Sort_K(nums, right, r, k - a - b);
}
int getRandom(vector<int>& nums, int l, int r)
{
return nums[rand() % (r - l) + l];
}
vector<int> inventoryManagement(vector<int>& stock, int cnt)
{
srand(time(NULL));
Quick_Sort_K(stock, 0, stock.size()-1, cnt);
return {stock.begin(), stock.begin() + cnt};
}
};
2. 分治:归并排序
关于归并排序方法的基本介绍可移步至此篇博文:
在算法中,归并排序思想并没有在原本排序的基础上有很大改动,时间复杂度为O(NlogN)。如果说快速排序算法 是一种前序 遍历排序,那么归并排序算法 就是一种后序 遍历排序:

在归并排序中,我们通常会新建一个临时数组用于存放原数组部分排序后的元素,而在算法中我们采取的方式是新建一个全局 数组tmp,每次归并后逐一赋值给原数组,这样可以省去创建和销毁局部变量的时间。
2.1 排序数组
解题思路:
这道题可以手搓任意时间复杂度小于等于O(NlogN)的排序解答,而归并排序的时间复杂度为O(NlogN),符合题意,算是一道练手题。
cpp
class Solution {
public:
vector<int> tmp;
void Merge_Sort(vector<int>& nums, int left, int right)
{
if(left >= right) return;
int mid = (left + right) / 2;
Merge_Sort(nums, left, mid);
Merge_Sort(nums, mid + 1, right);
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int i = left;
while(begin1 <= end1 && begin2 <= end2)
{
tmp[i++] = nums[begin1] <= nums[begin2] ? nums[begin1++] : nums[begin2++];
}
while(begin1 <= end1) tmp[i++] = nums[begin1++];
while(begin2 <= end2) tmp[i++] = nums[begin2++];
for(int i=left; i<=right; i++)
{
nums[i] = tmp[i];
}
}
vector<int> sortArray(vector<int>& nums)
{
tmp.resize(nums.size());
Merge_Sort(nums, 0, nums.size() - 1);
return nums;
}
};
2.2 交易逆序对的总数
解题思路:
- 暴力枚举
O(N^2):双层嵌套for循环枚举出所有两数组合,判断是否符合逆序对,会超时。 - 归并排序
O(NlogN):如果把数组分为两块,我们会发现这样一个规律:逆序对的组合无非就是左区间 / 右区间 / 一左一右 。

在左、右区间分别找完逆序对后,如何一左一右寻找逆序对呢?答案就是我们可以分别对左右区间排序 ,逐个比对(类似双指针):

但是排序的次数不止一次,区间的拆分也不只一层,据此我们可以间接联想到用归并排序 的方式解决。
升序版本 :

降序版本 :注意降序版本不能仅仅改变比较运算符,还要注意计算也会重复!

这道题在原本归并排序的基础上增加了ret这个细节,处理好后代码还是很好写的:
cpp
class Solution {
public:
vector<int> tmp;
int reversePairs(vector<int>& record)
{
tmp.resize(record.size());
return Merge_Sort1(record, 0, record.size() - 1);
}
//升序
int Merge_Sort1(vector<int>& nums, int left, int right)
{
if(left >= right) return 0;
int ret = 0;
int mid = (left + right) / 2;
//左个数 + 排序
ret += Merge_Sort(nums, left, mid);
//右个数 + 排序
ret += Merge_Sort(nums, mid + 1, right);
int cur1 = left;
int cur2 = mid + 1;
int i = 0;
//左右升序排序
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] <= nums[cur2])
{
tmp[i++] = nums[cur1++];
}
else
{
ret += mid - cur1 + 1;
tmp[i++] = nums[cur2++];
}
}
while(cur1 <= mid) tmp[i++] = nums[cur1++];
while(cur2 <= right) tmp[i++] = nums[cur2++];
for(int j=left; j<=right; j++)
{
nums[j] = tmp[j - left];
}
return ret;
}
// //降序
// int Merge_Sort(vector<int> &nums, int left, int right)
// {
// if(left >= right) return 0;
// int mid = (left + right) / 2;
// int ret = 0;
// ret += Merge_Sort(nums, left, mid);
// ret += Merge_Sort(nums, mid + 1, right);
// int cur1 = left;
// int cur2 = mid + 1;
// int i = 0;
// while(cur1 <= mid && cur2 <= right)
// {
// if(nums[cur1] > nums[cur2])
// {
// ret += right - cur2 + 1;
// tmp[i++] = nums[cur1++];
// }
// else
// {
// tmp[i++] = nums[cur2++];
// }
// }
// while(cur1 <= mid) tmp[i++] = nums[cur1++];
// while(cur2 <= right) tmp[i++] = nums[cur2++];
// for(int j=left; j<=right; j++)
// {
// nums[j] = tmp[j - left];
// }
// return ret;
// }
};
// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~