*51. 数组中的逆序对【归并排序】

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

示例 1:

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

限制:

  • 0 <= 数组长度 <= 50000

2. 标签

  • 归并排序

  • 分治算法

3. 解法 - 归并排序

3.1 Java

// 待琢磨
class Solution {
    public int reversePairs(int[] nums) {
        // 数组长度
        int length = nums.length;

        // 判空
        if (length < 2)
            return 0;

        // 复制一个数组
        int[] copy = new int[length];
        for (int i = 0; i < length; i++)
            copy[i] = nums[i];

        // 归并排序需要使用一个临时数组
        int[] temp = new int[length];
        // 对数组进行归并排序并且计算逆序对的数目
        return reversePairs(copy, 0, length - 1, temp);

    }

    // 计算逆序对数目并排序
    private int reversePairs(int[] nums, int left, int right, int[] temp) {
        // 判空
        if (left == right) {
            return 0;
        }

        // 中点
        int mid = left + (right - left) / 2;
        // nums[left, mid]中的逆序对数目
        int leftPairs = reversePairs(nums, left, mid, temp);
        // nums[mid+1, right]中的逆序对数目
        int rightPairs = reversePairs(nums, mid + 1, right, temp);

        // 左边数组的最后一个元素 比 右边数组的第一个元素 还要小
        // 说明两个数组之间没有逆序对,直接把两个分数组中的逆序对数目加起来即可
        if (nums[mid] <= nums[mid + 1]) {
            return leftPairs + rightPairs;
        }

        // 如果两个分数组中存在逆序对,归并排序并计算其逆序对的数目
        int crossPairs = mergeAndCount(nums, left, mid, right, temp);
        // 全部加到一起就是总的逆序对数目
        return leftPairs + rightPairs + crossPairs;
    }

    // 假设nums[left, mid] 和 nums[mid+1, right]都是有序的
    // 归并排序的同时计算逆序对的数目
    private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
        // 归并排序需要使用到一个临时数组
        for (int i = left; i <= right; i++) {
            temp[i] = nums[i];
        }

        // 指针 i 指向 nums[left, mid]
        int i = left;
        // 指针 j 指向 nums[mid+1, right]
        int j = mid + 1;

        // 逆序对的数目
        int count = 0;

        for (int k = left; k <= right; k++) {
            // 指针 i 遍历完了 nums[left, mid]
            // 接着遍历右边的数组
            if (i == mid + 1) { // 左指针遍历完左半块区域,把右半块区域剩余元素接上
                nums[k] = temp[j];
                j++;
                // 指针 j 遍历完了 nums[mid+1, right]
                // 接着遍历左边的数组
            } else if (j == right + 1) { // 右指针遍历完右半块区域,把左半块区域剩余元素接上
                nums[k] = temp[i];
                i++;
                // 不是逆序对,继续遍历
            } else if (temp[i] <= temp[j]) {
                nums[k] = temp[i];
                i++;
            } else { // temp[i] > temp[j] 发现逆序对,计算逆序对的数目
                nums[k] = temp[j]; //合并的过程中跨越两个区间的逆序对的个数
                j++;
                count += (mid - i + 1);
            }
        }
        return count;
    }
}

3.2 Kotlin

// 待琢磨
class Solution {
    fun reversePairs(nums: IntArray): Int {
        // 数组长度
        val length = nums.size

        // 判空
        if (length < 2)
            return 0

        // 复制一个数组
        val copy = IntArray(length)
        for (i in 0 until length)
            copy[i] = nums[i]

        val temp = IntArray(length)
        return reversePairs(copy, 0, length - 1, temp)

    }

    // 计算逆序对数目并排序
    private fun reversePairs(nums: IntArray, left: Int, right: Int, temp: IntArray): Int {
        // 判空
        if (left == right) {
            return 0
        }

        // 中点
        val mid = left + (right - left) / 2
        // nums[left, mid]中的逆序对
        val leftPairs = reversePairs(nums, left, mid, temp)
        // nums[mid+1, right]中的逆序对
        val rightPairs = reversePairs(nums, mid + 1, right, temp)

        // 左边数组的最后一个 比 右边数组的第一个 还要小
        // 说明两个数组之间没有逆序对,直接把两个分数组中的逆序对数目加起来即可
        if (nums[mid] <= nums[mid + 1]) {
            return leftPairs + rightPairs
        }

        // 如果两个分数组中存在逆序对,归并排序并计算其逆序对的数目
        val crossPairs = mergeAndCount(nums, left, mid, right, temp)
        // 全部加到一起就是总的逆序对数目
        return leftPairs + rightPairs + crossPairs
    }

    // 假设nums[left, mid] 和 nums[mid+1, right]都是有序的
    // 归并排序的同时计算逆序对的数目
    private fun mergeAndCount(nums: IntArray, left: Int, mid: Int, right: Int, temp: IntArray): Int {
        // 归并排序需要使用到一个临时数组
        for (i in left..right) {
            temp[i] = nums[i]
        }

        // 指针 i 指向 nums[left, mid]
        var i = left
        // 指针 j 指向 nums[mid+1, right]
        var j = mid + 1

        // 逆序对的数目
        var count = 0

        for (k in left..right) {
            // 指针 i 遍历完了 nums[left, mid]
            // 接着遍历右边的数组
            if (i == mid + 1) {
                nums[k] = temp[j]
                j++
                // 指针 j 遍历完了 nums[mid+1, right]
                // 接着遍历左边的数组
            } else if (j == right + 1) {
                nums[k] = temp[i]
                i++
                // 不是逆序对,继续遍历
            } else if (temp[i] <= temp[j]) {
                nums[k] = temp[i]
                i++
                // temp[i] > temp[j] 发现逆序对,计算逆序对的数目
            } else {
                nums[k] = temp[j]
                j++
                count += mid - i + 1
            }
        }
        return count
    }
}

3.3 复杂度分析

  • 时间复杂度 O(nlogn) :同归并排序。

  • 空间复杂度 O(n) :归并排序需要用到一个临时数组,占用 O(n) 大小的额外存储空间。

4. 参考

最后更新于