
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》
《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--优选算法
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
49.计算右侧小于当前元素的个数
题目链接:
题目描述:

题目示例:

解法(归并排序):
算法思路:
这一道题的解法与 求数组中的逆序对的解法是类似的,但是这一道题要求的不是求总的个数,而是要返回一个数组,记录每一个元素的右边有多少个元素比自己小。
但是在我们归并排序的过程中,元素的下标是会跟着变化 的,因此我们需要一个辅助数组 ,来将数组元素和对应的下标绑定在一起归并,也就是再归并元素的时候,顺势将下标也转移到对应的位置上。
由于我们要快速统计出某一个元素后面有多少个比它小的,因此我们可以利用求逆序对的第二种方法。
C++算法代码:
cpp
class Solution {
public:
vector<int> counts;
vector<int> index; //记录nums中当前元素的原始下标
vector<int> tmp;
vector<int> tmpi; //临时记录合并后对应元素位置变化下标随之变化的数组(保证元素和原始下标始终绑定)
vector<int> countSmaller(vector<int>& nums)
{
counts.resize(nums.size());
index.resize(nums.size());
tmp.resize(nums.size());
tmpi.resize(nums.size());
//初始化index,使得nums每个元素下标和index一一对应
for(int i = 0; i <nums.size(); i++)
{
index[i] = i;
}
mergesort(nums, 0, nums.size() - 1);
return counts;
}
void mergesort(vector<int>& nums, int left, int right)
{
if(left == right)
{
return;
}
int mid = (right - left) / 2 + left;
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])
{
counts[index[cur1]] += right - cur2 + 1; //重点
//因为index[cur1]始终是cur1当前元素的原始下标,所以不管当前元素位置怎么变化
//counts[index[cur1]]的位置都不会变
tmpi[i] = index[cur1];
tmp[i++] = nums[cur1++];
}
else
{
tmpi[i] = index[cur2];
tmp[i++] = nums[cur2++];
}
}
while(cur1 <= mid)
{
tmpi[i] = index[cur1];
tmp[i++] = nums[cur1++];
}
while(cur2 <= right)
{
tmpi[i] = index[cur2];
tmp[i++] = nums[cur2++];
}
for(int j = left; j <= right; j++)
{
nums[j] = tmp[j - left];
index[j] = tmpi[j - left];
}
}
};
算法总结及流程解析:

50.翻转对
题目链接:
题目描述:

题目示例:

题目解析:
翻转对和逆序对的定义大同小异,逆序对是前面的数要大于后面的数。而翻转对是前面的一个数要大于后面某个数的两倍。因此,我们依旧可以用归并排序的思想来解决这个问题。
解法(归并排序):
算法思路:
大思路与求逆序对的思路一样,就是利用归并排序的思想,将求整个数组的翻转对的数量,转换成三部分:左半区间翻转对的数量,右半区间翻转对的数量,一左一右选择时翻转对的数量。重点就是在合并区间过程中,如何计算出翻转对的数量。
与上个问题不同的是,上一道题我们可以一边合并一遍计算,但是这道题要求的是左边元素大于右边元素的两倍,如果我们直接合并的话,是无法快速计算出翻转对的数量的。
因此我们需要在归并排序之前完成翻转对的统计。
cpp
class Solution {
public:
vector<int> tmp;
int count = 0;
int reversePairs(vector<int>& nums)
{
tmp.resize(nums.size());
mergesort(nums, 0, nums.size() - 1);
return count;
}
void mergesort(vector<int>& nums, int left, int right)
{
if(left >= right)
{
return;
}
int mid = (right - left) / 2 + left;
mergesort(nums, left, mid);
mergesort(nums, mid + 1, right);
//这里需要注意:当我们左右区间排完序后,按照之前逆序对类似题目
//我们是求值和合并数组同时进行的
//因为左边大于右边正好就可以求值顺带可以进行合并操作
//但是这里求助的条件是左边需要大于右边的两倍
//也就是说如果左边大于右边我们不一定能够求值,但是必须要进行合并,就会出现问题
//所以如果不能同时进行操作我们就将求值和合并分开操作:先进行求值再进行合并数组
int cur1 = left, cur2 = mid + 1;
while(cur1 <= mid && cur2 <= right)//降序操作
{
//当左边大于右边的两边才会进行求值
if(nums[cur1] > (long long)2 * nums[cur2])
{
count += right - cur2 + 1;
cur1++;
}
else
{
cur2++;
}
}//当出了循环说明要么cur1>mid要么cur2>right,不管哪种情况此时翻转对是求完了
//无需像合并数组那样处理剩余部分
cur1 = left;
cur2 = mid + 1;
int i = 0;
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] > nums[cur2])
{
tmp[i++] = nums[cur1++];
}
else
{
tmp[i++] = nums[cur2++];
}
}
while(cur1 <= mid)
{
tmp[i++] = nums[cur1++];
}
while(cur2 <= right)
{
tmp[i++] = nums[cur2++];
}
// while(cur1 <= mid && cur2 <= right)//升序操作同样如此
// {
// 当左边大于右边的两边才会进行求值
// if(nums[cur1] > (long long)2 * nums[cur2])
// {
// count += mid - cur1 + 1;
// cur2++;
// }
// else
// {
// cur1++;
// }
// }
// cur1 = left;
// cur2 = mid + 1;
// int i = 0;
// while(cur1 <= mid && cur2 <= right)
// {
// if(nums[cur1] > nums[cur2])
// {
// tmp[i++] = nums[cur2++];
// }
// else
// {
// tmp[i++] = nums[cur1++];
// }
// }
// while(cur1 <= mid)
// {
// tmp[i++] = nums[cur1++];
// }
// while(cur2 <= right)
// {
// tmp[i++] = nums[cur2++];
// }
for(int i = left; i <= right; i++)
{
nums[i] = tmp[i - left];
}
}
};
算法总结及流程解析:

结束语
到此,49.计算右侧小于当前元素的个数,50.翻转对 这两道算法题就讲解完了。**49题通过维护原始下标数组,在归并过程中统计右侧较小元素数量;50题则需先统计满足条件的翻转对数量,再进行归并排序。**希望大家能有所收获!