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

题目链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
题目大意:给两个不定长正序数组,返回两个数组的中位数,要求O(log(m+n))

【解一】(可跳过)这题如果不要求时间复杂度那就很简单很暴力了,直接合并成有序的就可以。{ps:考虑一下合并最快的效率是什么?因为本来就是有序的,用归并排序是最快的,甚至也不需要全排,扫到中位数的位置就行了,O(m+n)}
当然这个复杂度是不符合要求的,但是离谱的事情是,随手交了下面这个代码准备验证一下差距的,结果力扣给的运行时间是与后文正经做法时间几乎没差= =所以我还是在这写出来了因为我想噗通= =

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m,n = len(nums1),len(nums2)
        nums = sorted(nums1+nums2)
        if (m+n)%2 == 1: return nums[(m+n)//2]
        else: return (nums[(m+n-1)//2]+nums[(m+n)//2])/2

【解二】好的我们从这里开始正经解题。以前写过一个类似的算法课作业,俩数组是等长,解法是每次比较两个数组的中位数,那么比较小数小的那一半数组和比较大数大的那一半数组都可以被排除了。这道题里数组是不等长的,但我们仍然可以往这个思路上靠,希望通过比较两个数组中的某两个数,就可以排除掉一批数据。只不过由于不等长,我们不能每次都规则的砍掉一半数据了。
首先考虑我们要找的这个中位数应该在什么位置,显然,就是假设两数组合并且有序:当m+n是奇数时,第(m+n+1)/2小的数;当m+n是偶数时,第(m+n+1)/2小的数和第(m+n+2)/2大的数的平均值。所以统一一下,我们只要定位到k=(m+n+1)/2就可以啦。
接下来拆开两个数组看,怎么在log级的复杂度内找到第k小的数,一个最自然的想法是二分。将k二分开,我们尝试在当前的A数组找前k/2的数跟B数组找前k/2的数来比较,假设末尾位置上的两个元素是a和b:如果a更小,则A组中a和小于a的这k/2个数一定没机会是第k个数了;如果b更小,则B组中b和小于b的这k/2个数一定没机会是第k个数。这样我们一次操作就淘汰了k/2个数,设置一下指针,问题就变成了在剩下的数据中找到第k/2小的数。
<举个栗子>:

①找第4小的数
A: 1 3 5 7
B: 2 4 6 8
淘汰1、3
②找第2小的数
A: 5 7
B: 2 4 6 8
淘汰2
③找第1小的数
A: 5 7
B: 4 6 8
第1小的数就直接找到了,是4

当然了,需要考虑一些边界条件。当其中一个数组中已经没剩下数了,直接拿出另一数组的第k个就行了。或者这个数组中还有数,但不够k/2个时,应该比较这串数组剩余的所有数(假设个数为l),和另一数组中的前k-l个数。具体就看代码吧~

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m,n = len(nums1),len(nums2)
        k = (m+n+1)//2
        id1,id2,med1,med2 = 0,0,0,0
        while k>0:
            if id1==m:
                med1 = nums2[id2+k-1]
                if id2+k<n: med2 = nums2[id2+k]
                break
            if id2==n:
                med1 = nums1[id1+k-1]
                if id1+k<m: med2 = nums1[id1+k]
                break
            if k==1:
                if nums1[id1]<nums2[id2]:
                    med1 = nums1[id1]
                    if id1+1<m: med2 = min(nums1[id1+1],nums2[id2])
                    else: med2 = nums2[id2]
                else:
                    med1 = nums2[id2]
                    if id2+1<n: med2 = min(nums2[id2+1],nums1[id1])
                    else: med2 = nums1[id1]
                break
            if (id1+ k//2 -1 < m)and(id2+ (k+1)//2 -1 < n):
                if nums1[id1+ k//2 -1] <= nums2[id2 + (k+1)//2 -1]:
                    id1 = id1 + k//2
                    k = (k+1)//2
                else:
                    id2 = id2 + (k+1)//2
                    k = k//2
            elif id1+ k//2 -1 >= m:
                k = k-(m-id1)
                if nums1[m-1]<nums2[id2+k-1]: id1 = m
                else:
                    id2 = id2 + k
                    k = m-id1
            else:
                k = k-(n-id2)
                if nums1[id1+ k -1]<=nums2[n-1]:
                    id1 = id1 + k
                    k = n-id2
                else: id2 = n
        if (m+n)%2 == 0: return (med1+med2)/2
        else: return med1

【解三】从上面特判边界的时候就可以看出来,其实还是能小小的优化一下的,因为A组和B组拿来比较的元素总和是等于k的,所以我们其实知道了一边的位置,就能确定另一边的位置了。拿这个思路改一下说法,我需要找到第k=(m+n+1)/2小的数,假设A数组在第i-1位和第i位截断,B数组在第j-1位和第j位截断,而i+j=k,且满足A[i-1]<B[j] && B[j-1]<A[i],则A[i-1]和B[j-1]中较大的数一定是第k小的数,A[i]和B[j]中较小的数就是第k+1小的数(m+n为偶数时用到)。这个很好证明,如果满足条件,那A[i-1]和B[j-1]之前都是小于它们的,A[i]和B[j]之后都是大于它们的,同时i+j=k嘛,位置上刚好是中位数。
根据这个思路,我应该在A中找到位置i,令j=k-i,就可以判断当前两个串被截断后满不满足情况。为了使用二分法精准定位一个i,我们将找一个最大的i符合A[i-1]<B[j]。
(这里多解释一下为什么是只判断A[i-1]<B[j]而不用再判断B[j-1]<A[i]了:假设当前的i已经是最大的满足A[i-1]<B[j]的i了,那么当i*=i+1时,j*=j-1,它们将不满足A[i*-1]<B[j*],代入后这相当于就是A[i]>B[j-1],自然的满足了上面的条件。)
当然,还有一些边界条件要仔细考虑,具体看代码吧~
这个算法我几乎复制粘贴了一次,让i在长度更小的那个数组上查找,也算是一丢丢小优化吧,因为按照这个思路来说,时间复杂度是O(log n)的,n等于被二分查找i的数组长度。

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m,n = len(nums1),len(nums2)
        if m<=n:
            left,right = 0,m
            med1,med2 = 0,0
            while left <= right:
                i = (left + right)//2
                j = (m + n + 1)//2 - i
                if i==0: temp1 = -1000000
                else: temp1 = nums1[i-1]
                if j==n: temp2 = 1000000
                else: temp2 = nums2[j]
                if temp1<=temp2:
                    if (j>0)and(temp1<nums2[j-1]): med1 = nums2[j-1]
                    else: med1 = temp1
                    if (i<m)and(temp2>nums1[i]): med2 = nums1[i]
                    else: med2 = temp2
                    left = i+1
                else:
                    right = i-1
        else:
            left,right = 0,n
            med1,med2 = 0,0
            while left <= right:
                i = (left + right)//2
                j = (m + n + 1)//2 - i
                if i==0: temp1 = -1000000
                else: temp1 = nums2[i-1]
                if j==m: temp2 = 1000000
                else: temp2 = nums1[j]
                if temp1<=temp2:
                    if (j>0)and(temp1<nums1[j-1]): med1 = nums1[j-1]
                    else: med1 = temp1
                    if (i<n)and(temp2>nums2[i]): med2 = nums2[i]
                    else: med2 = temp2
                    left = i+1
                else:
                    right = i-1
        if (m+n)%2==0: return (med1+med2)/2
        else: return med1 

末尾说句题外话TAT
我忏悔!说好一天一题的,结果这一题的题解我就断断续续写了三天TAT
即便如此感觉仍然没有说的很清楚。。希望接下来两天能补上来一点
写题解这件事真是比想象中心累很多啊orz

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值