1. 计算右侧小于当前元素的个数
解题思路:
本题思路和leetcode LCR170.交易逆序对类似,只不过在返回值上有所差异,本题需要返回一个数组 ,存储的是对应下标元素逆序对的个数。LCR170解法详见:
- 暴力枚举
O(N^2):逐个枚举数组中的元素,计算当前元素右侧小于当前元素的个数,放入数组中。 - 归并排序
O(NlogN):逆序对的找法可以根据LCR170的方式解决,即利用归并排序的方式优化暴力解法,依旧采用降序 的方式排序。

Q :但是要如何将逆序对的个数存入数组中呢?ret数组的下标应该如何对应到元素的下标呢?需要注意的是,由于归并排序改变了元素的位置,因此不能直接用ret[cur1]记录,否则就是刻舟求剑了。
A :解决方法是新建一个index数组,专门用于存放下标 ,改变nums中元素位置的同时,也改变index数组中对应元素下标的位置即可。注意index数组也需要创建对应的tmp数组:


这样算下来,我们就需要操作5个数组:nums,tmp_nums,index,tmp_in,ret。看上去很复杂,但捋顺思路后还是很好解决的:
cpp
class Solution {
public:
vector<int> ret;
vector<int> index;
vector<int> tmp_in;
vector<int> tmp_nums;
vector<int> countSmaller(vector<int>& nums)
{
int n = nums.size();
ret.resize(n);
index.resize(n);
tmp_in.resize(n);
tmp_nums.resize(n);
for(int i=0; i<n; i++)
{
index[i] = i;
}
Merge_Sort(nums, 0, n - 1);
return ret;
}
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 cur1 = left;
int cur2 = mid + 1;
int i = 0;
int j = 0;
while(cur1 <= mid && cur2 <= right)
{
if(nums[cur1] > nums[cur2])
{
tmp_nums[i++] = nums[cur1];
ret[index[cur1]] += right - cur2 + 1;
tmp_in[j++] = index[cur1++];
}
else
{
tmp_nums[i++] = nums[cur2];
tmp_in[j++] = index[cur2++];
}
}
while(cur1 <= mid)
{
tmp_nums[i++] = nums[cur1];
tmp_in[j++] = index[cur1++];
}
while(cur2 <= right)
{
tmp_nums[i++] = nums[cur2];
tmp_in[j++] = index[cur2++];
}
for(int k=left; k<=right; k++)
{
nums[k] = tmp_nums[k - left];
index[k] = tmp_in[k - left];
}
}
};
2. 翻转对
本题在LCR170的基础上增加了nums[i] > nums[j] * 2的限制条件,因此依然可以借助归并排序优化暴力算法。
解题思路:
- 暴力枚举
O(N^2):逐个枚举数组中的元素,统计所有的翻转对总数。 - 归并排序
O(NlogN):大致的思路和求逆序对的思路一致,不同之处在于,上一题我们可以在归并排序的同时 统计逆序对的数量,这道题由于统计规则的改变,不能同时进行 ,因此需要分步骤:先对统计左右区间翻转对的数量,再归并两个区间。
降序统计:
注意cur2指针在移动时是不必回退 的,因为cur1在不断减小 ,cur2前面的区间一定不满足 翻转对的条件:

升序统计:

时间复杂度 :由于两个指针在移动过程中都是不回退 的,在统计翻转对 时遍历了数组,归并排序 时再次遍历了数组,因此每层消耗的时间级数为N;递归深度 为logN,因此总的时间复杂度为O(logN)。
cpp
class Solution {
public:
vector<int> tmp;
int reversePairs(vector<int>& nums)
{
int n = nums.size();
tmp.resize(n);
return Merge_Sort(nums, 0, n - 1);
}
//降序
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)
{
while(cur2 <= right && nums[cur1] / 2.0 <= nums[cur2]) //除以2避免越界
{
cur2++;
}
if(cur2 > right) break; //优化:没有翻转对的时候直接退出
ret += right - cur2 + 1;
cur1++;
}
//合并两个有序数组
cur1 = left;
cur2 = mid + 1;
while(cur1 <= mid && cur2 <= right)
{
tmp[i++] = nums[cur1] >= nums[cur2] ? nums[cur1++] : 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;
}
};
// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~