LeetCode寻找两个有序数组的中位数JS实现

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

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

解法

暴力法

思路

将两个数组合并排序,然后返回中位数

复杂度

  • 时间复杂度: O ( m + n ) O(m+n) O(m+n)
  • 空间复杂度: O ( m + n ) O(m+n) O(m+n)

第k小

思路

题目是求中位数,其实就是求第 k 小数的一种特殊情况,而求第 k 小数有一种算法。

由于数列是有序的,其实我们完全可以一半儿一半儿的排除。假设我们要找第 k 小数,我们可以每次循环排除掉 k/2 个数。

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function (nums1, nums2) {
    let n = nums1.length
    let m = nums2.length
    let left = Math.floor((n + m + 1) / 2)
    let right = Math.floor((n + m + 2) / 2)
    //将偶数和奇数的情况合并,如果是奇数,会求两次同样的k
    return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5
};

function getKth(nums1, start1, end1, nums2, start2, end2, k) {
    let len1 = end1 - start1 + 1
    let len2 = end2 - start2 + 1

    if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k)
    // 保证如果有数组空了,一定是 len1 
    if (len1 == 0) return nums2[start2 + k - 1]
    // 如果len1空了,直接返回len2第k位

    if (k == 1) return Math.min(nums1[start1], nums2[start2])

    let i = start1 + Math.min(len1, Math.floor(k / 2)) - 1
    let j = start2 + Math.min(len2, Math.floor(k / 2)) - 1

    if (nums1[i] > nums2[j]) {
        return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1))
    }
    else {
        return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1))
    }
}

复杂度

  • 时间复杂度: O ( log ⁡ ( m + n ) ) O(\log{(m+n)}) O(log(m+n)),每进行一次循环,我们就减少 k/2 个元素,所以时间复杂度是 O ( log ⁡ ( m + n ) ) O(\log{(m+n)}) O(log(m+n))
  • 空间复杂度: O ( 1 ) O(1) O(1), 虽然我们用到了递归,但是可以看到这个递归属于尾递归,所以编译器不需要不停地堆栈

尾递归: 若函数在尾位置调用自身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。对尾递归的优化也是关注尾调用的主要原因。

尾递归在普通尾调用的基础上,多出了2个特征

  • 在尾部调用的是函数自身 (Self-called)
  • 可通过优化,使得计算仅占用常量栈空间 (Stack Space)

切片法

上述的方法最多需要执行 2n 个步骤。事实上,它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function (nums1, nums2) {
    let m = nums1.length
    let n = nums2.length
    if (m > n) {
        let temp = nums1; nums1 = nums2; nums2 = temp
        let tmp = m; m = n; n = tmp
    }
    let iMin = 0, iMax = m, halfLen = Math.floor((m + n + 1) / 2)
    while (iMin <= iMax) {
        let i = Math.floor((iMin + iMax) / 2)
        let j = halfLen - i
        if (i < iMax && nums2[j - 1] > nums1[i]) {
            iMin = i + 1
        }
        else if (i > iMin && nums1[i - 1] > nums2[j]) {
            iMax = i - 1
        }
        else {
            let maxLeft = 0;
            if (i == 0) { maxLeft = nums2[j - 1] }
            else if (j == 0) { maxLeft = nums1[i - 1] }
            else { maxLeft = Math.max(nums1[i - 1], nums2[j - 1]) }
            if ((m + n) % 2 == 1) { return maxLeft }

            let minRight = 0
            if (i == m) { minRight = nums2[j] }
            else if (j == n) { minRight = nums1[i] }
            else { minRight = Math.min(nums2[j], nums1[i]) }

            return (maxLeft + minRight) / 2.0
        }
    }
    return 0.0
}

复杂度

  • 时间复杂度: O ( log ⁡ ( m + n ) ) O(\log{(m+n)}) O(log(m+n)), 查找的区间是 [ 0 , m ] [0, m] [0,m]。而该区间的长度在每次循环之后都会减少为原来的一半。
  • 空间复杂度: O ( 1 ) O(1) O(1),我们只需要恒定的内存来存储 9 个局部变量, 所以空间复杂度为 O ( 1 ) O(1) O(1)

你可能感兴趣的:(LeetCode)