【路飞】寻找两个正序数组的中位数

172 阅读2分钟

记录 1 道算法题

寻找两个正序数组的中位数

4. 寻找两个正序数组的中位数 - 力扣(LeetCode)


要求:提供两个升序的数组,然后求出两个数组合并起来后的中位数。时间复杂度O(m+n)。

比如:nums1: [1,3] nums2: [2] 输出: 2。

如果不考虑时间复杂度的条件,只需要将两个数组进行归并就可以得到中位数,甚至只需要两个指针就可以得出。

考虑时间复杂度就只能使用二分,减少一部分遍历。中位数也就是第 n 个数,所以我们只要判断当前的数是不是第 n 个数。

首先将中位数是第 n 个数,将 n 平均分给两个数组,即每个数组得到的是 (n / 2) - 1 位置上的数,就是第 n / 2个。然后将两个数组的这个位置的数相比较,如果小的一方,那么可以判断为是前 n 个数中的一部分。

    n 是 3.5 就是前 (7 / 2) + 1 = 4 个数
    平均分配后 4 / 2 = 2
    nums1: 1 2 3 4
             ↑
    
    nums2: 5 6 7
             ↑
             
    2 < 6
    所以 1 和 2 属于 n 个数里面,移动指针,重新分配剩余的 n - 2。
    
    nums1: 1 2 3 4
    
    nums2: 5 6 7

还需要一些边界情况的考虑,比如由于长短不同,有一个数组不够分配。或者指针移到了数组的最后一个位置。

完整代码如下:

    function findMedianSortedArrays(nums1, nums2) {
        const total = nums1.length + nums2.length
        // 算出中位数是第几个,这里并不是下标
        const mid = total >> 1
        
        if (total % 2 === 1) {
            // 奇数个
            // 小数的情况下向下取整,所以要加 1
            return findKth(nums1, nums2, mid + 1)
        } else {
            // 偶数个
            // 只需要求两个不同的位置即可
            return (findKth(nums1, nums2, mid) + findKth(nums1, nums2, mid)) / 2
        }
    }
    
    function findKth(nums1, nums2, k) {
        // 两个指针
        let s1 = 0
        let s2 = 0
        
        while(true) {
            // 考虑一开始就已经被分好的情况
            // 当第一个数组被分配完,意味着中位数是第二个数组,第二个数组分配剩下的 k 个位置。
            if (s1 === nums1.length) {
                return nums2[s2 + k - 1]
            }
            // 第二个数组先分配完,同理
            if (s2 === nums2.length) {
                return nums1[s1 + k - 1]
            }
            // 如果 k 刚好分配剩下一个,那么小的数就是排在前面的第 n 个
            if (k === 1) {
                return Math.min(nums1[s1], nums2[s2])
            }
            // 分配 k 给两个数组
            let half = (k / 2) >> 1
            // 第一个数组被分配到的位置
            let newS1 = s1 + half - 1
            // 第二个数组被分配到的位置
            let newS2 = s2 + half - 1
            // 当数组没有超出边界的时候
            const a = nums1[newS1]
            const b = nums2[newS2]
            if (newS1 <= nums1.length - 1 && newS2 <= nums2.length - 1) {
                if (a < b) {
                    // 如果第一个数组的数小,则归类进前 n 个。
                    // 计算剩余的 k
                    k -= (newS1 - s1 + 1)
                    // 移动指针
                    s1 = newS1 + 1
                } else {
                    // 如果第二个数组的数小
                    k -= (newS2 - s2 + 1)
                    s1 = newS2 + 1
                }
            } else if (newS1 <= nums1.length - 1) {
                // 只有第一个数组还有数的时候
                // 只减 n1
                k -= newS1 - s1 + 1
                s1 = newS1 + 1
            } else if (newS2 <= nums2.length - 1) {
                // 只有第二个数组还有数的时候
                // 只减 n2
                k -= newS2 - s2 + 1
                s2 = newS2 + 1
            }
        }
    }