2 二分查找 leetcode4 寻找两个有序数组的中位数

题目描述

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

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0

示例2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

官方难度

Hard

解决思路

最简单粗暴的解决思路,从左到右遍历两个数组,找到中位数的位置:

  1. 计算nums1nums2的中位数,mid=(len(nums1)+len(nums2))/2,如果为长度和为偶数,则为mid=(len(nums1)+len(nums2))/2mid-1
  2. 同时从左到右遍历两个数组,如果nums1>nums2, nums2的下标++,否则nums1的下标++,直到找到第mid个数。

根据上面的分析,我们可以很容易的得到直接方案流程如下:

  1. 初始化start1start2代表当前nums1nums2的下标;
  2. 遍历mid+1次,每次找第i大的数,i从0开始,最终要找到第mid或者mid+1的数。
  3. 注意nums1或者nums2的下标越界问题。
  4. 如果nums1[start1]>nums2[start2]说明第i大的数字为nums2[start2]
  5. 如果i>=mid,可以结束循环,根据两个数组长度总和计算返回的中位数。
    基于这个流程,我们可以实现类似下面的代码:
class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        n_len = len(nums1) + len(nums2)
        mid = n_len / 2
        start1 = 0
        start2 = 0
        pre = 0
        current_val = 0
        #当前第[0,i]大的数,最终要求第mid和mid+1位置的数字
        for i in range(mid+1):
            pre = current_val
            if start1 > len(nums1)-1:
                current_val = nums2[start2]
                start2 += 1

            elif start2 > len(nums2)-1:   
                current_val = nums1[start1]
                start1 += 1

            elif nums1[start1] < nums2[start2]:
                current_val = nums1[start1]
                start1 += 1
                
            else:
                current_val = nums2[start2]
                start2 += 1
            if i >= mid:
                if n_len % 2 != 0: 
                    return current_val
                return float(current_val+pre)/2

优化

看到有序数组,马上想到二分查找的思路,假设中位数是求第mid个数,我们假设在第一个数组中求mid/2大的数字,第二个数组中求mid/2大的数字,然后比较这两个数字,如果数组1的结果大,其实可以保证数组2的第mid/2个数肯定是要小于两个数组合并后的第mid个数,因此可以排除第二个数组的前mid/2个数字,以输入nums1:[1,2,5,6], nums2:[3,5,6,7,9]为例子,
1.要求mid=(4+5+1)/2=5个数字,

  1. nums1中求第5/2大的数字,从nums2中求第5/2大的数字,
  2. nums1[2-1] = 2, nums2[2-1] = 5
  3. 可以将nums1中的1,2两个数字都去掉,也就变成求整个数组的[5,6], [3,5,6,7,9]第3大的数字。
  4. 一直求到第1大的数字时候,比较nums1[0]和nums2[0]的最小值则是最终结果。
  5. 需要注意数组越界,数组为空,下标和mid对应以及偶数长度数组和奇数长度数组的问题。

基于以上思路我们可以梳理流程如下:

  1. 计算nnums1的长度,mnums2的长度。
  2. 解决偶数长度和奇数长度的问题,通过求(n+m+1)/2和(n+m+2)/2两个位置的数字,将这两类问题合并为一类问题,最终结果为[(n+m+1)/2 位置的数字+(n+m+2)/2位置的数字]*0.5
  3. 定义递归函数,用于求第K大的数字
  4. 递归函数定义结束条件,k==1
  5. 判断是否数组越界
  6. 比较大小,然后递归求k - 去除数组的数量

基于这个流程,我们可以实现类似下面的代码:

class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        n = len(nums1)
        m = len(nums2)
        if not nums1:
            return (nums2[(n+m+1)/2-1] + nums2[(n+m+2)/2-1])*0.5
        if not nums2:
            return (nums1[(n+m+1)/2-1] + nums1[(n+m+2)/2-1])*0.5
        def getKth(nums1, start1, end1, nums2, start2, end2, k):  
            """
            start1: 数组1第一个元素开始位置
            end1: 数组1最后一个元素位置
            start2: 数组2第一个元素开始位置
            end2: 数组2最后一个元素位置
            k:需要求的第K大元素
            """
            if end1 < start1: 
                return nums2[start2+k-1]
            if end2 < start2: 
                return nums1[start1+k-1]
            if k == 1: 
                return min(nums1[start1], nums2[start2])
    
            start1_temp = min(start1+k/2-1, len(nums1)-1)
            start2_temp = min(start2+k/2-1, len(nums2)-1)

            if nums2[start2_temp] > nums1[start1_temp]:
                return getKth(nums1, start1_temp+1, end1, nums2, start2, end2, k-(start1_temp - start1+1))
            else:
                return getKth(nums1, start1, end1, nums2, start2_temp+1, end2, k-(start2_temp - start2+1))
        return (getKth(nums1, 0, n-1, nums2, 0,  m-1, (n+m+1)/2) + getKth(nums1, 0, n-1, nums2, 0,  m-1, (n+m+2)/2))*0.5
        

总结

这题真是费了太大力气才调通了,主要原因是数组的下标太多了,一定要注意定义好每一个变量的循环不变量,另外这道题求中位数,更可怕的是有两种情况,这次直接通过(n+m+1)/2和(n+m+2)/2来完美融合两种情况。

  1. 合并奇偶问题: (n+m+1)/2和(n+m+2)/2
  2. 下标问题,明确循环不变量,end代表最后一个元素位置,start代表第一个元素位置,k代表第k大的数字位置。
  3. 越界问题:取数组下标和数组长度的最小值。
  4. 删除数组问题:修改start位置,同时更改k的大小。
  5. 空数组问题,递归函数要判断是否end>start。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容