题目描述
给定一个数组nums,若nums[i] > 2 * nums[j],则称(i,j)为一个翻转对,求出数组中翻转对的数量。
思路
本题若直接使用双重循环,时间复杂度为0(N2)。测试用例会超时。所以需要将时间复杂度降到0(nlogn)。该题可考虑分治法。
假设数组A,若将A分成A1和A2,则A中翻转对的数量等于下标i和j均在A1中的翻转对数量加上下标i和j均在A2中的翻转对数量以及i和j分别在A1和A2中的数量。即:
s o l v e ( A ) = s o l v e ( A < s u b > 1 < / s u b > ) + s o l v e ( A < s u b > 2 < / s u b > ) + c r o s s m i d d l e ( A < s u b > 1 < / s u b > , A < s u b > 2 < / s u b > ) solve(A) = solve(A<sub>1</sub>) + solve(A<sub>2</sub>) + crossmiddle(A<sub>1</sub>,A<sub>2</sub>) solve(A)=solve(A<sub>1</sub>)+solve(A<sub>2</sub>)+crossmiddle(A<sub>1</sub>,A<sub>2</sub>)
所以我们可以通过分治法将问题分解为子问题,求解后再合并。这和归并排序的思想有所类似,而我们同时也可以通过归并排序对求解子问题的方法进行简化。
对于两个有序数组A1和A2,求解crossmiddle情况下的翻转对可以减少不必要的操作。对A2遍历,对每一个j,在A1中遍历,寻找满足nums[i] > 2 * nums[j]的i,若找到了,则i之后一直到mid的数都满足条件,因为是有序数组,i后的数字都要比nums[i]大。
这使得很多冗余操作被舍去。所以我们只需在归并排序中合并两个有序数组之前执行该操作,并将子问题结果合并即可。在这之后继续归并排序的操作,又一次得到两个有序数组,继续上述操作。直到数组有序,原问题的解也被合并了出来。
c++代码如下:
class Solution {
public:
int find_pairs(vector<int>& nums,int& left,int& right){
int mid = (left + right)/2;
int i = left,j = mid + 1;
int res = 0;
while(i <= mid){
while(j <= right && (long)nums[i] > 2 * (long)nums[j]){
res += mid - i + 1;
j++;
}
i++;
}
return res;
}
int merge_sort(vector<int>& nums,int nums_sort[],int left,int right){
if(left >= right) return 0;
int mid = (left + right) / 2;
int res = merge_sort(nums,nums_sort,left,mid) + merge_sort(nums,nums_sort,mid + 1,right) + find_pairs(nums,left,right);//求解当前数组的翻转对
int i = left,j = mid + 1,index = left;
while(i <= mid && j <= right){
if (nums[i] <= nums[j]) nums_sort[index++] = nums[i++];
else nums_sort[index++] = nums[j++];
}
while(i <= mid) nums_sort[index++] = nums[i++];
while(j <= right) nums_sort[index++] = nums[j++];
for (int index = left;index <= right;index++) nums[index] = nums_sort[index];
return res;//将结果返回至上一层
}
int reversePairs(vector<int>& nums) {
if (nums.empty()) return 0;
int left = 0,right = nums.size() - 1;
int nums_sort[nums.size()];
memset(nums_sort,0,sizeof(nums_sort));//数组初始化为0
return merge_sort(nums,nums_sort,left,right);
}
};