微软面试题:剑指 Offer 51. 数组中的逆序对 Hard 出现次数:3

题目描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

分析:

   本题的暴力方法显然容易想到,但是会报超时,难度等级 hard 显示 考察的是使用

时间O(log n*logn)空间 O(n)的解法。可以使用 「归并排序」 和 「线段树」 两种方法。

利用「归并排序」和「线段树」计算逆序对都是非常经典的做法。这里我们暂且只考虑

  利用「归并排序」计算逆序对。

 

思想是「分治算法」,所有的「逆序对」来源于 3 个部分:

  • 左边区间的逆序对;
  • 右边区间的逆序对;
  • 横跨两个区间的逆序对。

计算左边区间的逆序对和右边区间的逆序对都是规模更小的子问题,直接交给递归去完成。

重点是分析计算横跨两个区间的逆序对。计算横跨两个区间的逆序对时,上面的两个子问题都已经让递归函数完成了,

此时,左边区间的逆序对和右边区间的逆序对都已经计算出来了,且左边区间和右边区间都已经有序了。

计算横跨两个区间的逆序对 具体步骤如下:

1.  将给定区间 nums[l,r] 分成 左区间  nums[l , mid] ,右区间 nums[mid + 1,r] ,使用双指针同步遍历左右区间;

 

2.  如果左边区间当前的元素num[i] 小于等于 右边区间当前的元素nums [j],因为nums[i] 小于等于右边区间

所有的元素nums[j,r],nums[i]  不会和右区间内的元素nums[j,r] 构成逆序对。直接将nums[i] 放入归并排序的辅助空间。

 

3.   如果左边区间当前的元素num[i] 大于 右边区间当前的元素nums [j],那左边区间元素num[i,mid] ,一共 mid – i + 1 个都比

右边区间当前的元素nums[j]大,且都在右边区间当前的元素前面,和右边区间当前元素 构成  mid – i + 1  个逆序对。

加到总的逆序对数上,再将右边区间当前的元素nums [j] 放到缓冲区。

 

3.  将左(右)区间比较多出来的元素直接加到辅助空间中.

 

4. 将辅助空间中排序好的元素 再重新放回nums[l,r];

 

5. 函数返回当前区间计算得到的总的逆序对数。

 

代码如下:

 1 class Solution {
 2 public:
 3     vector<int> tmp;//归并排序的辅助数组
 4 
 5     int reversePairs(vector<int>& nums) {
 6         tmp.assign(nums.size(),0);
 7         return merge_sort(nums,0,nums.size() - 1);
 8     }
 9 
10     long long int merge_sort(vector<int>& nums,int l,int r)
11     {
12         if(l >= r)//归并排序递归出口
13         {
14             return 0;
15         }
16         int mid = l + (r - l)/2;//将区间一分为二
17         long long ans = merge_sort(nums,l,mid) + merge_sort(nums,mid + 1,r);//递归地求左右子数组的逆序对个数和
18         int k = 0,i = l,j = mid + 1;//计算 横跨左右区间的逆序对
19         while(i <= mid && j <= r)
20         {
21             if(nums[i] <= nums[j])
22             {
23                 tmp[k++] = nums[i++];
24             }
25             else
26             { //执行上述递归之后,左右子数组都已经排好序
27               //nums[i:mid] 都比nums[j] 大,都和num[j] 构成逆序对
28                 ans += (mid - i + 1);
29                 tmp[k++] = nums[j++];
30             }
31         }
32         while(i <= mid) tmp[k++] = nums[i++];
33         while(j <= r) tmp[k++] = nums[j++];
34         //将排序好的元素移回原数组,放在原来的区间上
35         for(i = l,j = 0;i <= r; )
36         {
37             nums[i++] = tmp[j++];
38         }
39         return ans;
40     }
41 };