数组中的逆序对

168 阅读3分钟

题目描述 (数组中的逆序对)

  • 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1

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

示例 2

输入: [7,5,6,4,7,6,8,5,6,4,5,6,7,8,9,4,5,3,2,1,3,4,5,7]
输出: 154

题解思路

  1. 通过题意,首先可能会想到的是对数组中的单个元素进行一一比较,如此我们就轻轻松松掉进了圈套,代码如下; 提交后发现会超时,次算法的时间复杂度为 O(n2)(n ^ 2),所以我们应该继续对时间进行优化。
public int reversePairs(int[] nums) {
        int n = nums.length;
        int total = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[i] > nums[j]) total++;  // 当左元素大于右元素时,total + 1
            }
        }
        return total;
    }
  1. 通过题意对比,可发现在问题求解的过程中与归并排序有着非常巧妙地相似度,所以可以尝试着分析。归并排序主要是 分解合并 两个过程,而每一次分解都会将当前数组分解为两个更小的子数组,每一次合并都将两个子数组合并成一个数组。例如(官方示例)
  • 假设我们有两个已排序的序列等待合并,分别是 L = { 8,12,16,22,100 } 和 R = { 9, 26, 55, 64, 91 }。一开始我们用指针 p1 = 0 指向 L 的首部,p2 = 0 指向 R 的头部。对于两个已经排好序的子数组,我们找出右子数组单个元素符合逆序对的个数,若 L + R 的长度为 n, 则时间复杂度,为 O(n)。相对于之前的暴力算法已经得到了大大的提升。
L = [8, 12, 16, 22, 100]   R = [9, 26, 55, 64, 91]
     |                          |
    p1                          p2
  • 依照上面的思路,可得出以下代码。本题本质上是对归并排序的应用,只要对归并排序有一定的理解就可以解决问题了。
public int reversePairs(int[] nums) {
        return mergeSort(nums);
    }
    int[] T;
    private int mergeSort(int[] nums) {
        int n = nums.length;
        T = new int[n];
        return sort(nums,0,n - 1);
    }

    private int sort(int[] nums, int l, int r) {
        if (l >= r) return 0;
        int m = l + (r - l) / 2;
        int left = sort(nums,l,m);  // 分为更小的子数组进行排序解决
        int right = sort(nums,m + 1,r);
        int count = merge(nums,l,m,r);
        return count + left + right;
    }

    private int merge(int[] nums, int l, int m, int r) {
        int l1 = l;
        int l2 = m + 1;
        int count = 0;
        for (int i = l; i <= r; i++) T[i] = nums[i];
        for (int i = l; i <= r; i++) {
            if      (l1 > m) nums[i] = T[l2++];
            else if (l2 > r) nums[i] = T[l1++];
            else if (T[l1] <= T[l2]) nums[i] = T[l1++];
            else {
                count += m + 1 - l1;  // 加上已排序好的右子数组大于当前左子数组元素的个数
                nums[i] = T[l2++];
            }
        }
        return count;
    }