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

70 阅读1分钟

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

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

算法:
方法一:排序
朴素思想,时间复杂度nlog(n+m)

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    nums1 = append(nums1, nums2...)
    n := len(nums1)
    sort.Ints(nums1)
    if n % 2 == 0 {
        return float64(nums1[n/2] + nums1[n/2 - 1]) / float64(2)
    }
    return  float64(nums1[n/2])
}

方法二:遍历 方法一没有利用到两个数组有序的特性,我们设置双指针,找到中位数和中位数前的那个数

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    m, n := len(nums1), len(nums2)
    left, right := 0, 0
    aStart, bStart := 0, 0 
    // i走了这么多步之后刚好到中位数的位置
    for i := 0; i <= (m + n) / 2; i ++ {
        left = right
        // 什么时候移动搞清楚
        if aStart < m && (n <= bStart || nums1[aStart] < nums2[bStart]) {
            right = nums1[aStart]
            aStart ++
        } else {
            right = nums2[bStart]
            bStart ++
        }
    }
    if (m + n) % 2 == 0 {
        return (float64(left + right)) / float64(2)
    }
    return float64(right)
}

方法三:二分
数组长度为len,则我们需要找第(len + 1) / 2小和第(len + 2) / 2小的数(也可以根据len的奇偶性,找第(len + 1) / 2小和第(len + 2) / 2小,或者第第(len + 1) / 2小小的数),找到之后计算结果即可。
问题转换为构造函数findKth,找到第k小的数。我们每次可以比较nums1[i + k / 2]和[j + k / 2]的大小,排除时间,然后缩小k,修改i,j继续查找。

小技巧:
1.第k小的数从1开始算的话,两个中位数是第(len + 1) / 2和(len + 2) / 2小的数。

2.getKth交换参数位置,让nums1的长度始终小于等于nums2

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    m, n := len(nums1), len(nums2)
    // 两个中位数是第(m + n + 1) / 2和(m + n + 2) / 2小的数
    return float64(getKth(nums1, 0, nums2, 0, (m + n + 1) / 2) + getKth(nums1, 0, nums2, 0, (m + n + 2) / 2)) / float64(2)
}

// k从1开始计数,找到第k小的数,i,j为nums1,nums2开始找最小数的位置
func getKth(nums1 []int, i int, nums2 []int, j int, k int) int {
    // 如果nums1的长度大于nums2, 则交换位置进行findKth
    if len(nums1) - i  > len(nums2) - j {
        return  getKth(nums2, j, nums1, i,  k)
    }
    // 如果nums1空了,直接返回nums2[j + k - 1]
    if i == len(nums1) {
        return nums2[j + k - 1]
    }
    // 如果k == 1了,比较nums1[0]和nums2[0]
    if k == 1 {
        return min(nums1[i], nums2[j])
    }
    // nums1,nums2第k小的数的位置,注意越界
    si := i + min(len(nums1), k / 2) - 1
    sj := j + min(len(nums2), k / 2) - 1
    if nums1[si] <= nums2[sj] {
        return getKth(nums1, si + 1, nums2, j,  k - (si - i + 1))
    } 
    return getKth(nums1, i, nums2, sj + 1,  k - (sj - j + 1))
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}