在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
分析:
暴力的方法很好想,但是难度是hard,而且数组长度是50000,可见暴力法必定超时。这里用的是分治法来降低时间复杂度。官方题解的视频已经讲得十分清晰明了了,我也不再班门弄斧,还是看官方题解吧。
这里只是稍微补充一下:我自己是没有想到题解中的思路的,只是看了题解之后自己把代码敲了出来。这个题的暴力法是显而易见的,但是对于第一次面对这个题目的人而言,如何优化?视频中直接提出了合并两个顺序数组,计算其逆序数。个人觉得从暴力到合并,这之间的跨度有些大。我在整理思路的时候,认为应该从此处入手:逆序数实质上是两个数之间大小关系的一种表示,暴力法就是枚举了任意两个数之间的大小关系,如果前者大于后者,则计入在内;否则略过。要想提高效率,要注意到大小关系是具有传递性的。比如说[5,4,2,6,3],与5有关的逆序数怎么计算,可以看一下与4有关的逆序数是2(4 > 2, 4 > 3),5 > 4的话必然有5 > 2, 5 > 3,由此看来应当是可以利用传递性进行优化的,传递性是需要数与数之间是有序的。同时降低时间复杂度的话,不出意外应该是O(nlogn)的算法,这个时候一般能想到分治。此时我们可以考虑一下,在分治的时候使用有序数组合并是否可行。而这个在视频里讲解的很清楚。
个人感觉这样思考的话,可能过渡更平缓一些,更容易让人接受。
class Solution {
public:
int cnt = 0;
void merge(int left, int mid, int right, vector<int> &nums){
vector<int> backup(right - left + 1);
int i = left, j = mid + 1, k = 0;
while(i <= mid && j <= right){
if(nums[i] <= nums[j]){
backup[k++] = nums[i++];
}else{
backup[k++] = nums[j++];
cnt += mid - i + 1;
}
}
while(i <= mid) backup[k++] = nums[i++];
while(j <= right) backup[k++] = nums[j++];
for(int i = left, k = 0; i <= right; i++, k++)
nums[i] = backup[k];
}
void merge_sort(int left, int right, vector<int> &nums){
if(left < right){
int mid = (left + right) / 2;
merge_sort(left, mid, nums);
merge_sort(mid + 1, right, nums);
merge(left, mid, right, nums);
}
}
int reversePairs(vector<int>& nums) {
merge_sort(0, nums.size() - 1, nums);
return cnt;
}
};