目录
- 0.原理讲解
- 1.排序数组
- 1.题目链接
- 2.代码实现
- 2.交易逆序对的总数
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
- 3.计算右侧小于当前元素的个数
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
- 4.翻转对
- 1.题目链接
- 2.算法原理详解
- 3.代码实现
0.原理讲解
- 归并排序的流程充分的体现了**「分⽽治之」**的思想,⼤体过程分为两步
- 分:将数组⼀分为⼆为两部分,⼀直分解到数组的⻓度为1 ,使整个数组的排序过程被分为「左半部分排序」+「右半部分排序」
- 治:将两个较短的**「有序数组合并成⼀个⻓的有序数组」**,⼀直合并到最初的⻓度
- 本质类似二叉树的后序遍历
1.排序数组
1.题目链接
- 排序数组
2.代码实现
- 递归时,创建vector开销挺大的 -> 辅助数组放全局
class Solution
{
vector<int> assist; // 归并时的辅助数组
public:
vector<int> sortArray(vector<int>& nums)
{
assist.resize(nums.size());
MergeSort(nums, 0, nums.size() - 1);
return nums;
}
void MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return;
}
// 1.选择中间点划分区间
int mid = left + (right - left) / 2;
// 2.排序左右区间
// [left, mid] [mid + 1, right]
MergeSort(nums, left, mid);
MergeSort(nums, mid + 1, right);
// 3.合并两个有序数组
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right)
{
assist[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
}
// 4.处理没有遍历完的数组
while(cur1 <= mid)
{
assist[i++] = nums[cur1++];
}
while(cur2 <= right)
{
assist[i++] = nums[cur2++];
}
// 5.还原
for(int i = left; i <= right; i++)
{
nums[i] = assist[i - left];
}
}
};
2.交易逆序对的总数
1.题目链接
- 交易逆序对的总数
2.算法原理详解
-
解法:利用归并排序的分治过程
- 左半部分 -> 左派序 -> 右半部分 -> 右排序 -> 一左一右 -> 排序
- 在归并排序的合并过程中统计出逆序对的数量
-
注意:默认都是升序,如果掌握升序,结合问题,降序的归并过程也是可以解决问题的
-
为什么可以利用归并排序?
- 如果将数组从中间划分成两个部分,那么可以将逆序对产⽣的⽅式划分成三组
- 全部从左数组中选择
- 全部从右数组中选择
- ⼀个选左数组另⼀个选右数组
- 根据排列组合的分类相加原理,三种情况下产⽣的逆序对的总和,正好等于总的逆序对数量
- ⽽这个思路正好匹配归并排序的过程
- 先排序左数组
- 再排序右数组
- 左右数组合二为一
- 因此,可以利⽤归并排序的过程
- 先求出左半数组中逆序对的数量
- 再求出右半数组中逆序对的数量
- 最后求出⼀个选择左边,另⼀个选择右边情况下逆序对的数量,三者相加即可
- 如果将数组从中间划分成两个部分,那么可以将逆序对产⽣的⽅式划分成三组
-
为什么要这么做?
- 在归并排序合并的过程中,得到的是两个有序的数组
- 可以利⽤数组的有序性,快速统计出逆序对的数量,⽽不是将所有情况都枚举出来
-
如何在合并两个有序数组的过程中,统计出逆序对的数量?
- 合并两个有序序列时求逆序对的⽅法有两种
- 快速统计出某个数前⾯有多少个数⽐它⼤ <- 升序
- 降序无法实现
- 快速统计出某个数后⾯有多少个数⽐它⼩ <- 降序
- 升序无法实现
- 快速统计出某个数前⾯有多少个数⽐它⼤ <- 升序
- 合并两个有序序列时求逆序对的⽅法有两种
-
法一:快速统计出某个数前⾯有多少个数⽐它⼤
- 在合并有序数组的时候,遇到左数组当前元素 > 右数组当前元素时,可以通过计算左数组中剩余元素的⻓度,就可快速求出右数组当前元素前⾯有多少个数⽐它⼤
- 在合并有序数组的时候,遇到左数组当前元素 > 右数组当前元素时,可以通过计算左数组中剩余元素的⻓度,就可快速求出右数组当前元素前⾯有多少个数⽐它⼤
-
法二:快速统计出某个数后⾯有多少个数⽐它⼩
- 在合并有序数组的时候,遇到左数组当前元素 <= 右数组当前元素时,可以通过计算右数组已经遍历过的元素的⻓度,快速求出左数组当前元素后⾯有多少个数⽐它⼤
- 在合并有序数组的时候,遇到左数组当前元素 <= 右数组当前元素时,可以通过计算右数组已经遍历过的元素的⻓度,快速求出左数组当前元素后⾯有多少个数⽐它⼤
3.代码实现
// v1.0 升序
class Solution
{
vector<int> assist;
public:
int ReversePairs(vector<int>& nums)
{
assist.resize(nums.size());
return MergeSort(nums, 0, nums.size() - 1);
}
int MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return 0;
}
int ret = 0;
// 选择中点,划分数组
int mid = left + (right - left) / 2;
// 左边的个数 + 排序 + 右边的个数 + 排序
// [left, mid] [mid + 1, right]
ret += MergeSort(nums, left, mid);
ret += MergeSort(nums, mid + 1, right);
// 一左一右的个数 + 排序
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] <= nums[cur2])
{
assist[i++] = nums[cur1++];
}
else
{
ret += mid - cur1 + 1;
assist[i++] = nums[cur2++];
}
}
// 处理未遍历完的数组
while(cur1 <= mid)
{
assist[i++] = nums[cur1++];
}
while(cur2 <= right)
{
assist[i++] = nums[cur2++];
}
// 还原
for(int i = left; i <= right; i++)
{
nums[i] = assist[i - left];
}
return ret;
}
};
----------------------------------------------------------------------------
// v2.0 降序
class Solution
{
vector<int> assist;
public:
int reversePairs(vector<int>& nums)
{
assist.resize(nums.size());
return MergeSort(nums, 0, nums.size() - 1);
}
int MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return 0;
}
int ret = 0;
// 选择中点,划分数组
int mid = left + (right - left) / 2;
// 左边的个数 + 排序 + 右边的个数 + 排序
// [left, mid] [mid + 1, right]
ret += MergeSort(nums, left, mid);
ret += MergeSort(nums, mid + 1, right);
// 一左一右的个数 + 排序
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] <= nums[cur2])
{
assist[i++] = nums[cur2++];
}
else
{
ret += right - cur2 + 1;
assist[i++] = nums[cur1++];
}
}
// 处理未遍历完的数组
while(cur1 <= mid)
{
assist[i++] = nums[cur1++];
}
while(cur2 <= right)
{
assist[i++] = nums[cur2++];
}
// 还原
for(int i = left; i <= right; i++)
{
nums[i] = assist[i - left];
}
return ret;
}
};
3.计算右侧小于当前元素的个数
1.题目链接
- 计算右侧小于当前元素的个数
2.算法原理详解
-
思路:解法与「求数组中的逆序对」的解法类似,但是本题要返回⼀个数组,记录每⼀个元素的右边有多少个元素⽐⾃⼰⼩
-
在归并排序的过程中,元素的下标是会跟着变化的
- 因此需要⼀个辅助数组,来将数组元素和对应的下标绑定在⼀起归并
- 也就是在归并元素的时候,顺势将下标也转移到对应的位置上
-
创建两个全局的数组
vector<int> ret
-> 记录结果vector<int> index
-> 记录下标
3.代码实现
class Solution
{
vector<int> ret;
vector<int> index;
vector<int> assistNums;
vector<int> assistIndex;
public:
vector<int> CountSmaller(vector<int>& nums)
{
int n = nums.size();
ret.resize(n);
index.resize(n);
assistNums.resize(n);
assistIndex.resize(n);
// 初始化index
for(int i = 0; i < n; i++)
{
index[i] = i;
}
MergeSort(nums, 0, n - 1);
return ret;
}
void MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return;
}
// 中间点,划分数组
int mid = left + (right - left) / 2;
// [left, mid] [mid + 1, right]
// 先处理左右子数组
MergeSort(nums, left, mid);
MergeSort(nums, mid + 1, right);
// 处理一左一右 + 排序(降序)
// 元素和下标同步迁移
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] <= nums[cur2])
{
assistNums[i] = nums[cur2];
assistIndex[i++] = index[cur2++];
}
else
{
ret[index[cur1]] += right - cur2 + 1; // 统计 -> 重点
assistNums[i] = nums[cur1];
assistIndex[i++] = index[cur1++];
}
}
// 处理未遍历完数组
while(cur1 <= mid)
{
assistNums[i] = nums[cur1];
assistIndex[i++] = index[cur1++];
}
while(cur2 <= right)
{
assistNums[i] = nums[cur2];
assistIndex[i++] = index[cur2++];
}
// 还原
for(int i = left; i <= right; i++)
{
nums[i] = assistNums[i - left];
index[i] = assistIndex[i - left];
}
}
};
4.翻转对
1.题目链接
- 翻转对
2.算法原理详解
-
思路解析:
- ⼤思路与求逆序对的思路⼀样,利⽤归并排序的思想,将求整个数组的翻转对的数量,转换成三部分:
- 左半区间翻转对的数量,右半区间翻转对的数量,⼀左⼀右选择时翻转对的数量
- 重点就是在合并区间过程中,如何计算出翻转对的数量
- 与上个问题不同的是,上⼀道题可以⼀边合并⼀遍计算,但是这道题要求的是左边元素⼤于右边元素的两倍,如果直接合并的话,是⽆法快速计算出翻转对的数量的
- 与上个问题不同的是,上⼀道题可以⼀边合并⼀遍计算,但是这道题要求的是左边元素⼤于右边元素的两倍,如果直接合并的话,是⽆法快速计算出翻转对的数量的
- ⼤思路与求逆序对的思路⼀样,利⽤归并排序的思想,将求整个数组的翻转对的数量,转换成三部分:
-
计算翻转对:利用单调性,使用同向双指针
- 法一:计算当前元素后面,有多少元素的两倍比我小 -> 降序
- 法二:计算当前元素之前,有多少元素的一半比我大 -> 升序
3.代码实现
// v1.0 降序
class Solution
{
vector<int> assist;
public:
int reversePairs(vector<int>& nums)
{
assist.resize(nums.size());
return MergeSort(nums, 0, nums.size() - 1);
}
int MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return 0;
}
int ret = 0;
// 中间点,划分两区间
int mid = left + (right - left) / 2;
// [left, mid] [mid + 1, right]
// 先计算左右子区间翻转对
ret += MergeSort(nums, left, mid);
ret += MergeSort(nums, mid + 1, right);
// 计算一左一右翻转对的数量
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid) // 降序 固定cur1
{
// * -> / 防溢出
// / 2.0确保能除尽
while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0)
{
cur2++;
}
// 优化
if(cur2 > right)
{
break;
}
ret += right - cur2 + 1;
cur1++;
}
// 合并两个有序数组
cur1 = left, cur2 = mid + 1;
while(cur1 <= mid && cur2 <= right)
{
assist[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];
}
// 处理未遍历完数组
while(cur1 <= mid)
{
assist[i++] = nums[cur1++];
}
while(cur2 <= right)
{
assist[i++] = nums[cur2++];
}
// 还原
for(int i = left; i <= right; i++)
{
nums[i] = assist[i - left];
}
return ret;
}
};
-------------------------------------------------------------------------
// v2.0 升序
class Solution
{
vector<int> assist;
public:
int reversePairs(vector<int>& nums)
{
assist.resize(nums.size());
return MergeSort(nums, 0, nums.size() - 1);
}
int MergeSort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return 0;
}
int ret = 0;
// 中间点,划分两区间
int mid = left + (right - left) / 2;
// [left, mid] [mid + 1, right]
// 先计算左右子区间翻转对
ret += MergeSort(nums, left, mid);
ret += MergeSort(nums, mid + 1, right);
// 计算一左一右翻转对的数量
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur2 <= right) // 升序 固定cur2
{
while(cur1 <= mid && nums[cur2] >= nums[cur1] / 2.0)
{
cur1++;
}
// 优化
if(cur1 > mid)
{
break;
}
ret += mid - cur1 + 1;
cur2++;
}
// 合并两个有序数组
cur1 = left, cur2 = mid + 1;
while(cur1 <= mid && cur2 <= right)
{
assist[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
}
// 处理未遍历完数组
while(cur1 <= mid)
{
assist[i++] = nums[cur1++];
}
while(cur2 <= right)
{
assist[i++] = nums[cur2++];
}
// 还原
for(int i = left; i <= right; i++)
{
nums[i] = assist[i - left];
}
return ret;
}
};