• 微软面试题:剑指 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 };
  • 相关阅读:
    HDU 1175 连连看 (DFS+剪枝)
    CF702F T-Shirts
    UVA12538 Version Controlled IDE
    P2605 [ZJOI2010]基站选址
    P3835 【模板】可持久化平衡树
    CF915E Physical Education Lessons
    P3701 「伪模板」主席树
    P1198 [JSOI2008]最大数
    P3466 [POI2008]KLO-Building blocks
    P3919 【模板】可持久化数组(可持久化线段树/平衡树)
  • 原文地址:https://www.cnblogs.com/wangxf2019/p/14110706.html
Copyright © 2020-2023  润新知