天天看点

LeetCode - 4. 寻找两个正序数组的中位数

作者:写代码的lorre

题目

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]

输出:2.00000

解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]

输出:2.50000

解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -10^6 <= nums1[i], nums2[i] <= 10^6

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/median-of-two-sorted-arrays

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

解题步骤:

  1. 如何查找有序数组的中位数
  2. 如何查找有序数组第k位数
  3. 优化

如何查找有序数组的中位数

我们来看看如何查找有序数组的中位数:

LeetCode - 4. 寻找两个正序数组的中位数

数组长度为奇数

当数组长度为奇数n时,有一位中位数,为:(n + 1) / 2

LeetCode - 4. 寻找两个正序数组的中位数

数组长度为偶数

当数组长度为偶数n时,有两位中位数,分别为:n / 2 和 (n + 2) / 2

所以题目就变成了:

  • 当m + n为奇数时,查找第 (m + n + 1) / 2 位数
  • 当m + n为偶数时,查找第 (m + n) / 2 和 (m + n + 2) / 2 位数

如何查找有序数组第k位数

我们定义两个变量i和j,i代表数组nums1的下标,j代表数组nums2的下标,初始值都为0

下面我们只需要解决两个问题:

  1. 如何移动边界i和j
  2. 判定查找到目标值

如何移动边界i和j

LeetCode - 4. 寻找两个正序数组的中位数

边界

  1. 比较nums1[i]和nums2[j]值的大小
  • 当nums1[i] <= nums2[j]时,此时nums1[i]肯定不是第k位数,所以此时需要移动边界i,最简单的方式就是移动一位i++
  • 当nums1[i] > nums2[j]时,此时nums2[j]肯定不是第k位数,所以此时需要移动边界j,最简单的方式就是移动一位j++
  1. 经过步骤1,边界已经移动了一位,所以需要查找目标值的位数也需要改变k--
  2. 循环继续下一次查找

判定查找到目标值

  1. 当i >= nums1的长度时,此时只用查找nums2即可,目标值下标为:j + k - 1
  2. 当j >= nums2的长度时,此时只用查找nums1即可,目标值下标为:i + k - 1
  3. 当k == 1时,此时需要比较nums1[i]和nums2[j]值的大小
  • 当nums1[i] <= nums2[j]时,此时目标值为nums1[i]
  • 当nums1[i] > nums2[j]时,此时目标值为nums2[j]

把以上思想整理成go代码如下:

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    nums1Len := len(nums1)
    nums2Len := len(nums2)
    // 特殊判断
    if nums1Len + nums2Len == 0 {
        return 0.0
    }
    if (nums1Len + nums2Len) % 2 == 0 {
        // 数组长度为偶数,需要查找两个中位数
        return float64(findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len) / 2) + findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len + 2) / 2)) / 2.0
    } else {
        // 数组长度为奇数,需要查找一位中位数
        return float64(findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len + 1) / 2))
    }
}

func findK(nums1 []int, i int, nums1Len int, nums2 []int, j int, nums2Len int, k int) int {
    // 判断左边界是否越界
    if i >= nums1Len {
        return nums2[j + k - 1]
    }
    // 判断右边界是否越界
    if j >= nums2Len {
        return nums1[i + k - 1]
    }
    // 判断第一位是否就是目标值
    if k == 1 {
        if nums1[i] <= nums2[j] {
            return nums1[i]
        } else {
            return nums2[j]
        }
    }
    // 判断如何移动边界
    if nums1[i] <= nums2[j] {
        i++
    } else {
        j++
    }
    k--
    return findK(nums1, i, nums1Len, nums2, j, nums2Len, k)
}           

优化

题目给出了两个重要信息:

  • 正序(从小到大)数组
  • 时间复杂度应该为 O(log (m+n))

正序 + 时间复杂度为log,基本就可以联想到二分法了

以上代码中,我们可以优化的地方是移动边界的逻辑:每次简单的移动一位 -> 每次移动 k / 2 位,减少循环查找次数

优化代码:

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    nums1Len := len(nums1)
    nums2Len := len(nums2)
    // 特殊判断
    if nums1Len + nums2Len == 0 {
        return 0.0
    }
    if (nums1Len + nums2Len) % 2 == 0 {
        // 数组长度为偶数,需要查找两个中位数
        return float64(findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len) / 2) + findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len + 2) / 2)) / 2.0
    } else {
        // 数组长度为奇数,需要查找一个中位数
        return float64(findK(nums1, 0, nums1Len, nums2, 0, nums2Len, (nums1Len + nums2Len + 1) / 2))
    }
}

func findK(nums1 []int, i int, nums1Len int, nums2 []int, j int, nums2Len int, k int) int {
    // 判断左边界是否越界
    if i >= nums1Len {
        return nums2[j + k - 1]
    }
    // 判断右边界是否越界
    if j >= nums2Len {
        return nums1[i + k - 1]
    }
    // 判断第一位是否就是目标值
    if k == 1 {
        if nums1[i] <= nums2[j] {
            return nums1[i]
        } else {
            return nums2[j]
        }
    }
    // 优化移动边界逻辑,每次移动 k / 2
    num1 := math.MaxInt
    if i + k/2 - 1 < nums1Len {
        num1 = nums1[i + k/2 - 1]
    }
    num2 := math.MaxInt
    if j + k/2 - 1 < nums2Len {
        num2 = nums2[j + k/2 - 1]
    }
    if num1 <= num2 {
        i = i + k/2
    } else {
        j = j + k/2
    }
    return findK(nums1, i, nums1Len, nums2, j, nums2Len, k - k/2)
}           

继续阅读