题目链接
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
解题思路
暴力法(超时)
public class Solution {public int reversePairs(int[] nums) {int cnt = 0;int len = nums.length;for (int i = 0; i < len - 1; i++) {for (int j = i + 1; j < len; j++) {if (nums[i] > nums[j]) {cnt++;}}}return cnt;}}
归并排序(分治法)
高级排序算法(快速排序,归并排序)中,能够看到非常明显的阶段排序结果的算法就是归并排序。
预备知识
「归并排序」是分治思想的典型应用,它包含这样三个步骤:
- 分解: 待排序的区间为 [l, r][l,r],令 m=⌊(l+r)/2⌋,我们把 [l, r] 分成 [l, m] 和 [m + 1, r]
- 解决: 使用归并排序递归地排序两个子序列
- 合并: 把两个已经排好序的子序列 [l, m] 和 [m + 1, r] 合并起来
在待排序序列长度为 11 的时候,递归开始「回升」,因为我们默认长度为 11 的序列是排好序的。
而计算逆序数就发生在排序的过程中,利用了「排序」以后数组的有序性。
- 关键在于「合并两个有序数组」的步骤,利用数组的部分有序性,一下子计算出一个数之前或者之后元素的逆序的个数;
- 前面「分」的时候什么都不做,「合」的过程中计算「逆序对」的个数;
- 「排序」的工作是必要的,正是因为「排序」才能在下一轮利用顺序关系加快逆序数的计算,也能避免重复计算;
- 在代码实现上,只需要在「归并排序」代码的基础上,加上「逆序对」个数的计算,计算公式需要自己在草稿纸上推导。
下面提供两种写法:
1、在第 2 个子区间元素归并回去的时候,计算逆序对的个数(参考代码 2);
2、在第 1 个子区间元素归并回去的时候,计算逆序对的个数(参考代码 3)。
注意:两者不能同时计算,否则会计算重复。
参考代码 2:在第 2 个子区间元素归并回去的时候,计算逆序对的个数。


即在 j 指向的元素赋值回去的时候,给计数器加上 mid - i + 1。
class Solution {public:int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {if(l>=r)return 0;int mid = l + (r-l)/2; // 避免l和r过大时,l+r溢出// 递归计算左右两边int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);int i = l,j=mid+1,pos=l;while (i <= mid && j <= r) { // 归并if (nums[i] <= nums[j]) { // 当左边小于等于右边时,插入左边tmp[pos] = nums[i];++i;}else { // 当右边小于左边时,插入右边,并将逆序数加上左边剩余的元素个数tmp[pos] = nums[j];++j;inv_count += (mid - i + 1);}++pos;}// 将剩余的元素插入for (int k = i; k <= mid; ++k) {tmp[pos++] = nums[k];}for (int k = j; k <= r; ++k) {tmp[pos++] = nums[k];}// 将辅助数组排序好的部分复制给元素数组// [ _First,_Last) DestBeg 指出复制到的目标区间起始位置// copy(First,Last,DestBeg);copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);// 等同于下面// for(int i=l;i<=r;i++){// nums[i]= tmp[i];// }return inv_count;}int reversePairs(vector<int>& nums) {int n = nums.size();if(n<2)return 0;vector<int> tmp(n); //辅助数组return mergeSort(nums, tmp, 0, n - 1);}};
class Solution {public int reversePairs(int[] nums) {return binarySort(nums, 0, nums.length - 1);}private int binarySort(int[] nums, int left, int right){if(left >= right){return 0;}int res = 0;int mid = left + (right - left)/2;res += binarySort(nums, left, mid);res += binarySort(nums, mid+1, right);int[] result = new int[right - left + 1];int l = left, r = mid + 1;int pos = 0;while(l <= mid && r <= right){if(nums[r] < nums[l]){result[pos++] = nums[r++];res += (mid - l + 1);}else{result[pos++] = nums[l++];}}while(l <= mid){result[pos++] = nums[l++];}while(r <= right){result[pos++] = nums[r++];}pos = 0;for(int i = left; i <= right; ++i){nums[i] = result[pos++];}return res;}}
- 时间复杂度:O(NlogN): 这里 N 是数组的长度。复杂度是归并排序的时间复杂度,直接看递归树的结点个数或者使用主定理分析,归并的回收每一步计算逆序对的个数是 O(1) 的;
- 空间复杂度:O(N)。
