剑指 Offer 51. 数组中的逆序对(C++实现)

172 阅读2分钟

题目地址:leetcode-cn.com/problems/sh…

题目描述

image.png

解题思路

解这道题我们可以采取归并排序的方法,在归并排序的分治过程中统计数组的逆序对个数。

归并排序

  • 是否稳定:是
  • 时间复杂度:O(nlogn)
  • 基本步骤:
    1. 确定出口条件
    2. 确定左右区间分界点
    3. 递归分治
    4. 归并排序
    5. 将排好序的临时数组更新至原数组 image.png

分析问题(该部分为转载)

版权声明:本文为CSDN博主「芜湖男童」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq_41046821…

  • 计算逆序对的过程中分为三种情况:
    1. 两个元素都在左边;
    2. 两个元素都在右边;
    3. 两个元素一个在左一个在右; 因为归并排序最终会把数组分为长度为1的多个小区间,因此实际上都会来到第三种情况。
  • 算法的大致框架为:
    1. 递归算左边的;
    2. 递归算右边的;
    3. 算一个左一个右的; 最后把三个结果加在一起。

具体代码(C++)

class Solution {
public:
    int reversePairs(vector<int>& nums) {
      return merge_sort(nums, 0 ,size(nums) - 1);
    }

    int merge_sort(vector<int>& nums, int l, int r) {
      // 确定出口条件
      if(l >= r) return 0;

      // 确定分界点
      int mid = l + ((r - l) >> 1);

      // 递归分治,找出左右两个区间的逆序对并相加
      int res = merge_sort(nums, l, mid) + merge_sort(nums, mid + 1, r);

      // 归并排序
      int i = l, j = mid + 1, k = 0;
      int tmp[r - l + 1];
      while(i <= mid && j <= r) {
        // 左区间的数大于右区间的数,说明无逆序对
        if(nums[i] <= nums[j]) tmp[k++] = nums[i++];
        // 右区间的数大于左区间的数,说明存在逆序对
        else {
          tmp[k++] = nums[j++];
          // 归并排序会使左右区间的数分别有序
          // 若nums[i] > nums[j],则说明nums[i+1...mid]也大于nums[j],即逆序对为mid-i+1个
          /* 注意:尽管此时数组的排序与原来不同,但不影响我们得出最终结果。
             因为分治会使原数组分成多个长度为1的小区间,我们分别对这些小区间求逆序对,
             然后依次合并且重复步骤,直到合并成一个大区间为止。(我们关注的是左右区间两个整体的 
             相对顺序,其内部顺序发生变化对整体没有影响)*/
          res += mid - i + 1;
        }
      }
      while(i <= mid) tmp[k++] = nums[i++];
      while(j <= r) tmp[k++] = nums[j++];

      // 将排好序的数组更新至原数组
      for(i = 0; i < k; i++) nums[l + i] = tmp[i];

      return res;
    }
};

总结

此题让我迷惑的点在于排好序后的数组相较于原数组的顺序发生了变化,这是否会影响最终的结果。就数组[7, 5, 6. 4]来说,不管是分为[7,5]、[6, 4]还是[5, 7]、[4, 6],其形成的逆序对数量相同。因为分治会使原数组分成多个长度为1的小区间,我们已经计算了最小情况下的逆序对,当它依次进行排序合并时我们只需关注合并后各个区间整体,而区间内部相对顺序的变化不会影响最终结果。