leetCode每日一题 寻找中位数

该博客介绍了如何在O(log(m+n))的时间复杂度内找到两个正序数组的中位数。通过分析题目,提出了三种解法:1) 合并数组并排序;2) 不合并数组,直接比较;3) 使用二分查找法。重点讨论了二分查找法的思路,确保不会过早判断中位数,并提供了相关代码示例。

题目

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。

分析
单看题目寻找中位数而言,可以简化为寻找两个数列中的第n位数字,可以通过利用指针,对于两个数列中大小比较的,直至找到最中间的一个数或两个数,由于要求时间复杂度为O(log(m + n)),可以得知题主可能想使用要用到二分法寻找指定数,如果抛去时间复杂度这个附加条件,我们可以用一种比较简单和粗暴的解法来判断。
1.合并为一个数列
这个解法比较简单,将两个数列合并到一个数列中去,然后排序,若数列长度 m+n 为奇数,则 (n+m)/2 的位置即为中位数,若为偶数,则为中间两位数的和的二分之一,具体代码如下

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

}`

在这个代码中,使用的是sort包,具体源代码如下,使用的是快速排序的方法

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}
/ Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
    // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
    n := data.Len()
    maxDepth := 0
    for i := n; i > 0; i >>= 1 {
        maxDepth++
    }
    maxDepth *= 2
    quickSort(data, 0, n, maxDepth)
}

所以用这种方法的时间复杂度为 O(m+n),空间复杂度也为 O(m+n),在这里插入图片描述
2.不合并为一个数组,简化空间复杂度
直接两个数列进行比较,当比较出大小时,指针则向后移动一位,并将此值赋予输出值,这种方法麻烦在于,什么时候移动a组数列的指针,什么时候移动b组数列的指针,奇偶的时候如何划分,当数列总长度为奇数的时候,我们只需要取中间第k次的数值即可,当为偶数的时候,我们则需要上一次的输出结果,所以我们还需要一位数储存上一次的输出结果。
具体代码如下:

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    n := len(nums1)
    m := len(nums2)
    s := n+m
    a :=0
    b :=0
    outFinal :=0
    out :=0
    for i:=0; i<=s/2;i++{
        outFinal=out
        if  a < n && (b >= m || nums1[a] < nums2[b]){            
            out = nums1[a]
            a++
        }else{
            out = nums2[b]
            b++
        }
    }
    if s%2!=0 {
        return float64(out)
    } else{
        return float64(out+outFinal)/2
    }
}

这种方法同样繁琐,尤其是要考虑指针移动时,可能超出数列长度的问题,因为仍然经历了两个数列,所以时间复杂度依旧为O(m+n), 空间复杂度为O(1),因为只有几个特定值

3. 二分查找
二分查找可以大批量的解决掉运算复杂的问题,每一次的计算可以减下一半的计算量,在这道题中,可以简化为不断寻找中位数进行比较,具体原理如下。
首先,既然时间复杂度为O(log(m+n)),所以必然是将两个数列看做是同一个数列,再用二分法计算一半的量,假如有十一个数列,我们可以尝试分解他的解题思路。
首先,正如前面所说,本题的实质就是在找第k个最小数,则我们可以用二分法逐渐逼近第 k 小的数字来实现算法,则可以把两组数列中 K/2 个数字相比较,来去掉最小的两个数。我们可以直接比较A[K/2]和B[K/2],若A[K/2]<B[K/2],则之前的一定也小于中位数。
很多人可能疑惑,有没有可能中位数出现过于早,而后面的数字过于大,而过早的判定错误中位数,换句简单的话就是,有没有可能中位数在k/2之前就出现,这是不可能的,我们可以稍后再提。在这里插入图片描述
如图,我们要找的是第6小的数字,比较可以直接,有3个数字应该小于中位数,即舍弃的是数列nums中的 nums1[1], nums1[2], nums1[3]。
然后将剩下的数组3,4, 5,6, 7,8, 9,10, 11放在一起,继续找第6小的数,因为已经删除了两位数字,所以只需要再删除三位数字就可以了,所以K=6-3=3,则 K/2=1,则有红色部分为删除掉的数列,则删掉4,留下567891011,继续比较,K=2,K/2=1,则有
在这里插入图片描述
则6为第六小的数字,即中位数。
然后讨论有无可能中位数在 K/2之前,其实是不可能的,因为比中位数大的数字不可能出现在中位数之前,一旦中位数在数列中的次序提前,则后续数字也将增大,且一旦中位数的位置提前,大量比中位数小的数字会出现在另一数列中去,举例而言
在这里插入图片描述
把6向后移动一点在这里插入图片描述
具体代码如下(两种):

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    n:= len(nums1)
    m:= len(nums2)
    s:= len(nums1) + len(nums2)
    if s%2 != 0 {
        K := s/2
        return float64(getKth(nums1, n,nums2, m,K+1))
    } else {
        K1, K2 := s/2-1 , s/2
        return float64(getKth(nums1, n ,nums2,m, K1+1) + getKth(nums1,n,nums2,m, K2+1)) / 2.0
    }
    return 0
}
func getKth(nums1 []int, n int, nums2 []int, m int, k int) int {
	var temp[] int
    var test int
    if n > m{
        temp =nums1
        nums1=nums2
        nums2 = temp
        test = n
        n = m
        m = test
    }
	if n == 0 {
		return nums2[k-1]
	}
	if k == 1 {
		return min(nums1[0], nums2[0])
	}
    i := min(k/2,n)
	if nums1[i-1] > nums2[i-1] {
		return getKth(nums1, n, nums2[i:], m-i, k-i)
        } else {
		return getKth(nums1[i:], n-i, nums2, m, k-i)
	}
}
func min(x, y int) int {
    if x < y {
        return x
    }
    return y
}
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    totalLength := len(nums1) + len(nums2)
    if totalLength%2 == 1 {
        midIndex := totalLength/2
        return float64(getKthElement(nums1, nums2, midIndex + 1))
    } else {
        midIndex1, midIndex2 := totalLength/2 - 1, totalLength/2
        return float64(getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0
    }
    return 0
}

func getKthElement(nums1, nums2 []int, k int) int {
    index1, index2 := 0, 0
    for {
        if index1 == len(nums1) {
            return nums2[index2 + k - 1]
        }
        if index2 == len(nums2) {
            return nums1[index1 + k - 1]
        }
        if k == 1 {
            return min(nums1[index1], nums2[index2])
        }
        half := k/2
        newIndex1 := min(index1 + half, len(nums1)) - 1
        newIndex2 := min(index2 + half, len(nums2)) - 1
        pivot1, pivot2 := nums1[newIndex1], nums2[newIndex2]
        if pivot1 <= pivot2 {
            k -= (newIndex1 - index1 + 1)
            index1 = newIndex1 + 1
        } else {
            k -= (newIndex2 - index2 + 1)
            index2 = newIndex2 + 1
        }
    }
    return 0
}

func min(x, y int) int {
    if x < y {
        return x
    }
    return y
}

时间复杂度为O(log(m+n)),也可以尝试两个数列分别取中位数然后比较的方法,时间复杂度为O(logm*n)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值