【分治】归并排序
1. 排序数组
1. 1题目来源
1.2 题目描述
给你一个整数数组 nums,请你将该数组升序排列。
- 示例 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
1.3 题目解析
本次实现我们使用归并的思想,这里也可以使用其他的排序算法,比如快排。而快排我们在上一期也进行讲解过了——分支-快速排序,快排的原理在八大排序算法中也有详细的讲解,不了解的可以复习一下。
class Solution {
public:
void MergeSort(vector<int>& nums, int left, int right, vector<int>& temp)
{
if (left >= right) return;
//1. 分解
int mid = left + (right - left) / 2;
MergeSort(nums, left, mid, temp);
MergeSort(nums, mid + 1, right, temp);
// 合并
int l = left, r = mid + 1;
int k = left;
while (l <= mid && r <= right)
{
if (nums[l] < nums[r])
temp[k++] = nums[l++];
else temp[k++] = nums[r++];
}
while (l <= mid)
temp[k++] = nums[l++];
while (r <= right)
temp[k++] = nums[r++];
// 3. 返回
for (int i = left; i <= right; i++)
{
nums[i] = temp[i];
}
}
vector<int> sortArray(vector<int>& nums)
{
vector<int> temp;
int n = nums.size();
temp.resize(n);
MergeSort(nums, 0, n - 1, temp);
return nums;
}
};
2. LCR 170. 交易逆序对的总数
2. 1题目来源
[LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/
2.2 题目描述
在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。
示例 1:
输入:record = [9, 7, 5, 4, 6]
输出:8
解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。
限制:
0 <= record.length <= 50000
2.3 题目解析
这里我们一开始想到的是直接暴力枚举的,但是可以看到0 <= record.length <= 50000,暴力枚举的时间复杂度是O(N^2)肯定是会超时的。所以这里我们不能使用暴力枚举。
所以这里我们想到了使用归并的方法,这里我们回顾一下归并的方法。归并的方法是先求出中间节点,将其分成左右部分,接着根据左右两部分再次重复上述动作,直到划分出只有一个节点之后,就开始进行重新合并,在此之间就可以进行排序的动作。
所以在合并的时候我们就可以来实现找逆序对的动作。在我们实现归并的动作时我们可以在实现排序的动作之前将逆序对计算出来。而这个时候对于下次归并来讲是已经有序的了,而对于有序的左右两部分的话我们要计算左右中的逆序对是很好计算的。
因为再上一步我们已经计算出来左右各部分的逆序和,所以就算排好序了也不会影响计算左右的逆序对,反而会更好计算了。
class Solution {
public:
void MergeSort(vector<int>& nums, int left, int right, vector<int>& temp, int &ret)
{
if (left >= right) return;
int mid = left + (right - left) / 2;
MergeSort(nums, left, mid, temp, ret);
MergeSort(nums, mid + 1, right, temp, ret);
int k = left, l = left, r = mid + 1;
while (l <= mid && r <= right)
{
if (nums[l] <= nums[r])
temp[k++] = nums[l++];
else
{
ret += mid - l + 1;
temp[k++] = nums[r++];
}
}
while (l <= mid)
temp[k++] = nums[l++];
while (r <= right)
temp[k++] = nums[r++];
for (int i = left; i <= right; i++)
nums[i] = temp[i];
}
int reversePairs(vector<int>& record) {
int ret = 0;
vector<int> temp;
int n = record.size();
temp.resize(n);
MergeSort(record, 0, n - 1, temp, ret);
return ret;
}
};
3. 计算右侧小于当前元素的个数
3. 1题目来源
3.2 题目描述
给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
- 示例 1:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素- 示例 2:
输入:nums = [-1]
输出:[0]- 示例 3:
输入:nums = [-1,-1]
输出:[0,0]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
3.3 题目解析
本题其实和上一题LCR 170. 交易逆序对的总数其实是一样的道理,只不过上一题要求的是总数,而本题求的是每一个数据对应前面比他小的数的个数。而这个时候如果我们使用hash的话,数据又是可以重复的无法使用哈希。所以这里最大的难点在于我们在进行归并的时候如何快速的定位到数据,并根据该数据随对应的原始下标进行计数。
所以这里我们要用到一个辅助数组。
所以这个时候我们需要定义两个辅助数组,一个辅助原始数组,一个辅助index数组。
采用降序的方式,进行比较。
所以具体步就是定义temp,index,index_temp, ret四个容器,ret用来存放返回的数据,temp用来辅助原始数组,index用来存放与原始数组的绑定,indx_temp用来辅助index。
- 先直接进行归并的常规操作
- 判断nums[l] > nums[r]是否成立,如果成立说明找到了一个数据后面的数比前面的数要写,并需要更新此时l标识数据的下标存放到index_temp中。
- 后序同样进行归并的操作,并更新小标索引
- 不要忘记重新将辅助数组中的数据放回原来的数组中。
class Solution {
public:
vector<int> temp,index,index_temp, ret;
void MergeSort(vector<int>& nums, int left, int right)
{
if (left >= right) return;
int mid = left + (right - left) / 2;
MergeSort(nums, left, mid);
MergeSort(nums, mid + 1, right);
int l = left, r = mid + 1;
int k = left;
while (l <= mid && r <= right)
{
if (nums[l] > nums[r])
{
ret[index[l]] += right - r + 1;
index_temp[k] = index[l];
temp[k++] = nums[l++];
}
else
{
index_temp[k] = index[r];
temp[k++] = nums[r++];
}
}
while (l <= mid)
{
index_temp[k] = index[l];
temp[k++] = nums[l++];
}
while (r <= right)
{
index_temp[k] = index[r];
temp[k++] = nums[r++];
}
for (int i = left; i <= right; i++)
{
nums[i] = temp[i];
index[i] = index_temp[i];
}
}
vector<int> countSmaller(vector<int>& nums)
{
int n = nums.size();
temp.resize(n);
index.resize(n);
index_temp.resize(n);
ret.resize(n);
for (int i = 0; i < n; i++) index[i] = i;
MergeSort(nums, 0, n - 1);
return ret;
}
};