[待完善]leetcode 4. 寻找两个正序数组的中位数

560 阅读3分钟

以下三道题环环相扣,前面的题的解法可以用来解后面的题。先解决问题1,然后在问题1的基础上解决问题2,再在问题2的基础上解决问题3。

  1. NC36 在两个长度相等的排序数组中找到上中位数
  2. CD82 在两个排序数组中找到第k小的数
  3. leetcode 4. 寻找两个正序数组的中位数

问题1

解题方法有多种

  • 放在一起,整体排序
  • 按照归并排序的“一趟合并”过程,但不做真的合并,只移动指针,找到上中位数即返回
  • 二分
  • 划分数组

下面具体分析最后一种 “划分数组” 的解题方法。已知两数组等长且有序,先分析可能出现的多种情况,

  • 数组的长度为0
    • 不符合题目给定的输入数据限制
  • 数组的长度为1
    • 较小的那个值就是上中位数
  • 数组的长度大于等于2
    • 中值相等
      • 数组的长度为奇数 设一个数组为a=[0,1,2,3,4],另一个为a'=[0',1',2',3',4']。注意,数组里的数是元素的索引(从0开始),不是具体的值。“中值相等”,那么就有2==2'。显然,此时上中位数就是2(或者2')。
      • 数组的长度为偶数 设一个数组a=[0,1,2,3],另一个数组a'=[0',1',2',3']。“中值相等”,那么就有1==1'。显然,此时上中位数就是1(或者1')。
    • 一个中值大于另一个中值
      • 数组的长度为奇数
      • 数组的长度为偶数
    • 一个中值小于另一个中值(这种情况和前一种是类似的)
      • 数组的长度为奇数
      • 数组的长度为偶数

问题2

思路:

问题3

思路:根据有序数组的性质,不断抛弃不存在所需数据的区间。
这确实是一道难题,需要细致分析多种情况。最好的办法就是在纸上分情况推演。

// 入口函数
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    l1 := len(nums1)
    l2 := len(nums2)
    if l1 == 0 && l2 == 0 {
        panic("nums1 and nums2 are all empty")
    }
    if l1 == 0 {
        return getMedian(nums2)
    }
    if l2 == 0 {
        return getMedian(nums1)
    }
    // l1 > 0 && l2 > 0
    l := l1+l2
    if l&1 != 0 {
        return float64(getKthSmallest(nums1, nums2, l>>1+1))
    }
    return (float64(getKthSmallest(nums1, nums2, l>>1))+float64(getKthSmallest(nums1, nums2, l>>1+1)))/2
}

// 找到两个有序数组的第k小的数
// k >= 1 && k <= len(a1)+len(a2)
func getKthSmallest(a1, a2 []int, k int) int {
    var (
        l1 = len(a1)
        l2 = len(a2)
    )
    if k < 1 || k > l1+l2 {
        panic("k is invalid")
    }
    var (
        sl = l1 // 短数组的长度
        sa = a1 // 短数组
        ll = l2 // 长数组的长度
        la = a2 // 长数组
    )
    if l1 > l2 {
        sl = l2
        sa = a2
        ll = l1
        la = a1
    }
    if k <= sl {
        return getUpMedian(a1[:k], a2[:k])
    }
    if k > ll {
        li := k-sl-1 // 长数组的索引
        if la[li] >= sa[sl-1] {
            return la[li]
        }
        si := k-ll-1
        if sa[si] >= la[ll-1] {
            return sa[si]
        }
        return getUpMedian(sa[si+1:], la[li+1:])
    }
    // k > sl && k <= ll
    li := k-sl-1 // 长数组索引
    if la[li] >= sa[sl-1] {
        return la[li]
    }
    if la[k-1] <= sa[0] {
        return la[k-1]
    }
    return getUpMedian(sa, la[li+1:k])
}

// 找到两个长度相等的有序数组的上中位数
func getUpMedian(a1, a2 []int) int {
    var (
        l1 = len(a1)
        l2 = len(a2)
    )
    if l1 != l2 {
        panic("len(a1) != len(a2)")
    }
    var (
        b1 int
        e1 = l1-1
        b2 int
        e2 = l2-1
    )
    for b1 < e1 && b2 < e2 {
        m1 := b1+(e1-b1)>>1
        m2 := b2+(e2-b2)>>1
        l := e1-b1+1
        if a1[m1] == a2[m2] {
            return a1[m1]
        }
        if a1[m1] > a2[m2] {
            if l&1 != 0 {
                e1 = m1
                b2 = m2
            } else {
                e1 = m1
                b2 = m2+1
            }
        } else {
            if l&1 != 0 {
                b1 = m1
                e2 = m2
            } else {
                b1 = m1+1
                e2 = m2
            }
        }
    }
    return min(a1[b1], a2[b2])
}

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

// 计算一个有序数组的中位数
func getMedian(nums []int) float64  {
    l := len(nums)
    if l == 0 {
        panic("nums is empty")
    }
    if l&1 != 0 {
        return float64(nums[l>>1])
    }
    return (float64(nums[l>>1-1])+float64(nums[l>>1]))/2
}

时间复杂度O(log(min(m,n)))O(log(min(m,n))),空间复杂度O(1)O(1)