[路飞]数组中的逆序对

104 阅读3分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。

记录 1 道算法题

数组中的逆序对

leetcode-cn.com/problems/sh…

要求:给一个数组 [7,5,6,4],输出:5。里面有逆序对 5 对。分别是 [7,5] [6,4] [7,6] [7,4] [5,4]。

通过对每个数进行对比,可以实现暴力求解。只要用一个指针遍历,然后每次都与后面的数进行比较即可。前面的数大于后面的数就是逆序对。

那么使用了算法优化的话,要怎么实现呢?

这里要介绍一下归并排序,归并排序是一种排序算法,利用的是分治的思想,即将两个数组进行归并,每次从某个数组中拿一个。要求是拿两个数组中最小的。所以归并排序也有一个条件,那就是需要两个数组是有序数组,这样每次只需要比较数组中的第一个即可。

但是题目给了一个数组,怎么分成两个数组呢,这就是归并排序的分治思想,首先会把数组从中间分成两半,这样就有两个数组了。如果继续再切分就会得到四个数组,分裂是指数级别的增长。

一直切分的话可以使用递归,那么到最后递归的终止条件就是数组无法继续切分,就是当数组都变成一个一个的时候。然后进行合并,一级级往上返回的数组就会变成有序数组。

为什么会变成有序数组呢? 这就涉及到合并多个数组的思路了。归并是采用分组合并的方式,另外还有以一个为主数组,其他都并入的做法。相关的题目可以搜索“合并k个链表”相关的题目。

首先切到最细,一个一个数组,长度都为 1,这时候两两一组进行合并,小的排在前面,就会得到多个长度为 2 的有序数组。例如 [2,7] [5,6]。这时候,归并排序已经可以确保这两个数组合成的数组也是有序的,[2,5,6,7]。一层层返回的时候都进行合并,这是递归的代码。

归并排序介绍完了,那么这道题怎么解呢?这道题并不是要求排序。

其中的细节就在归并排序的过程中。 我们会对两个数组进行遍历。并且这两个数组是有序的。那么我们可以知道只要左边的数组有一个值大于右边的数组,那么意味着左边的数组剩下的数都将大于右边的数组当前的数。比如 [3,4,5] [2,6,8]。当我们知道 3 > 2 的时候,4 必然大于 2, 5 也是。可以得到逆序对 3 个。

所以我们在归并的时候如果发现了左边的数组第 i 个大于右边的数组的某个数。那么逆序对的个数就是 左边数组.length - i 个。

那么为什么同组的不用考虑可能会有逆序对呢?

因为其实已经计算过了。在下一层的时候,即 1 * 1 组合的时候已经开始进行归并,当前看到的数组是曾经的左右两个数组。

下面是完整实现

    function reversePairs(nums) {
        if (nums.length <= 1) return 0

        let count = 0

        function merge(nums) {
            // 终止条件
            if (nums.length === 1) return nums
            const mid = nums.length >> 1
            const left = nums.slice(0, mid)
            const right = nums.slice(mid)
            // 递归
            const a = merge(left)
            const b = merge(right)

            let i = 0
            let j = 0
            let res = []
            while (i < a.length && j < b.length) {
                if (a[i] <= b[j]) {
                    res.push(a[i])
                    i++
                } else {
                    res.push(b[j])
                    // 计数
                    count += a.length - i
                    j++
                }
            }

            if (i < a.length) {
                for (let q = i; q < a.length; q++) {
                    res.push(a[q])
                }
            } else if (j < b.length) {
                for (let q = j; q < b.length; q++) {
                    res.push(b[q])
                }
            }

            return res
        }

        merge(nums)
        return count
    }