微软面试题:剑指 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 };
上一篇:BZOJ2406矩阵——有上下界的可行流+二分答案


下一篇:潭州课堂25班:Ph201805201 第九课 函数作用域和匿名函数 (课堂笔记)