LeetCode0004 寻找两个有序数组的中位数

该博客详细介绍了如何利用分治和二分法解决LeetCode第0004题——寻找两个有序数组的中位数。通过分析中位数的特性,提出使用较短数组的索引进行二分查找,逐步缩小搜索范围,最终达到O(log(m + n))的时间复杂度。博主给出了具体的Python3解题代码。
摘要由CSDN通过智能技术生成

题目描述

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例

示例1:

nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0

示例2:

nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3) / 2 = 2.5

求解思路

【注】思路主要是给本人自己看的,以便忘了能记起。

        首先,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:

将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。

        如果是两个等长的数组,那么我们可以分别求2个数组的中位数,比较大小后,各自删除一半,反复如此,直到两边数组都只剩一个元素为止,最终中位数就是这两个元素的平均数。
        然而,此题没要要求两个数组长度必须一致,难就难在这里,所以只能模拟枚举。整体的中位数把元素分割成 l e f t \bf \blue {left} left r i g h t \bf \blue{right} right两个集合,我们就要探测在这两个集合中nums1nums2数组各有多少个元素在里面。
        简单的方法是类似合并两个有序列表的算法,时间复杂度是 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n)),但是题目要求的是 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)),显然是要用到递归,不然时间复杂度不可能出现对数。
        怎么用递归呢,参考两个数组长度相等找中位数的算法,每次删去一部分,直到最后剩下结果。现在数组长度不相等了,这种二分元素显然行不通,所以退而求其次,二分索引,并且是长度较小的数组的索引。为什么是较短的数组?在后面编码时你会发现,探测长数组的索引可能会导致一些麻烦,需要额外的工作。
        那么,二分所引怎么用呢?通常情况,是设置两个哨兵 i _ m a x i\_max i_max i _ m i n i\_min i_min,探测位置 i = f u n c ( i _ m i n , i _ m a x ) i=func(i\_min,i\_max) i=func(i_min,i_max),通过判断 i i i是偏大还是偏小,来调整 i _ m a x i\_max i_max i _ m i n i\_min i_min值,然后继续这一步,直到找到满足中位数要求的 i i i

-------------------------------------下面开始具体算法-------------------------------------

首先,根据数组长度进行排序,令短的为nums1,长的为nums2m = len(nums1), n = len(nums2)
我们将nums1分成两个部分,左边 i i i个元素,右边 m − i m-i mi个元素,其中 0 ≤ i ≤ m 0\le i \le m 0im

l e f t _ o f _ n u m s 1 \bf{left\_of\_nums1} left_of_nums1 r i g h t _ o f _ n u m s 1 \bf{right\_of\_nums1} right_of_nums1
i = 0 i=0 i=0Nonenums1
0 &lt; i &lt; m 0&lt;i&lt;m 0<i<mnums1[0],nums1[1],…,nums1[i-1]nums1[i],nums1[i+1],…,nums2[m-1]
i = m i = m i=mnums1None

同理,将nums2分成两个部分,左边 j j j个元素,右边 n − i n-i ni个元素:

l e f t _ o f _ n u m s 2 \bf{left\_of\_nums2} left_of_nums2 r i g h t _ o f _ n u m s 2 \bf{right\_of\_nums2} right_of_nums2
j = 0 j=0 j=0Nonenums2
0 &lt; j &lt; m 0&lt;j&lt;m 0<j<mnums2[0],nums2[1],…,nums2[j-1]nums2[j],nums2[j+1],…,nums2[n-1]
j = m j = m j=mnums2None

l e f t _ o f _ n u m s 1 \bf{left\_of\_nums1} left_of_nums1 l e f t _ o f _ n u m s 2 \bf{left\_of\_nums2} left_of_nums2放入集合 l e f t \bf \blue {left} left r i g h t _ o f _ n u m s 1 \bf{right\_of\_nums1} right_of_nums1 r i g h t _ o f _ n u m s 2 \bf{right\_of\_nums2} right_of_nums2放入集合 r i g h t \bf \blue {right} right如果我们可以确认:
        ① l e n ( l e f t ) = l e n ( r i g h t )   或 者 l e n ( l e f t )   =    l e n ( r i g h t ) + 1 len(\blue {left}) = len( \blue {right}) \ 或者len(\blue {left})\ =\ \ len( \blue {right})+1 len(left)=len(right) len(left) =  len(right)+1
        ② m a x ( l e f t ) ≤ m i n ( r i g h t ) max(\blue {left}) \le min( \blue {right}) max(left)min(right)

那么,我们已经将 nums1nums2中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
那么:
m e d i a n = m a x _ o f _ l e f t + m i n _ o f _ r i g h t 2 \bf \blue {median} = \frac{\blue {max\_of\_left} + \blue {min\_of\_right}}{2} median=2max_of_left+min_of_right
要确保上面两个条件,我们只需保证:
        ① c o u n t _ o f _ l e f t = l e n ( l e f t ) = ⌈ m   +   n 2 ⌉ \bf \blue {count\_of\_left} = len(\blue {left}) = \lceil \frac{m\ +\ n}{2}\rceil count_of_left=len(left)=2m + n
        ② i   +   j   =   c o u n t _ o f _ l e f t i\ +\ j\ =\ \bf \blue {count\_of\_left} i + j = count_of_left
        ③ m a x ( max( max(nums1[i-1],nums2[j-1] ) ≤ m i n ( ) \le min( )min(nums[i],nums2[j] ) ) )

【注】边界情况为简单情况,单独讨论即可,上面为一般情况下的做法

结合之前所说,可以这么做:
        把上面要满足的条件②放到else语句里,这样,可以把特殊情况的解答也放在else里,减少代码冗余;而if语句用来调整 i _ m i n i\_min i_min i _ m a x i\_max i_max的值.

Python3解答

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m, n = len(nums1), len(nums2)

        # 这里为了保证nums1一定是长度较小的数组
        if m > n:
            nums1, nums2, m, n = nums2, nums1, n, m

        # 题目给定数组不会同时为空,也就是m^2+n^2≠0,由于m≤n,故只要n≠0即可
        if not n:
            raise ValueError("数组长度不同时为零")

       	i_min, i_max = 0, m
       
       	# left集合元素数量,如果m+n是奇数,left比right多一个数据
        count_of_left = (m + n + 1) // 2  

        while i_min <= i_max:
            i = (i_min + i_max) // 2  				# left有i个nums1的元素
            j = count_of_left - i  					# left有j个nums2的元素
            if i > 0 and nums1[i - 1] > nums2[j]:
                i_max = i - 1						# i太大,要减少
            elif i < m and nums1[i] < nums2[j - 1]:
                i_min = i + 1						# i太小,要增加
            else:
                if i == 0:
                    max_of_left = nums2[j - 1]
                elif j == 0:
                    max_of_left = nums1[i - 1]
                else:
                    max_of_left = max(nums1[i - 1], nums2[j - 1])

                if (m + n) % 2:
                    return float(max_of_left)				# 结果是浮点数

                if i == m:
                    min_of_right = nums2[j]
                elif j == n:
                    min_of_right = nums1[i]
                else:
                    min_of_right = min(nums1[i], nums2[j])

                return (max_of_left + min_of_right) / 2.0	# 结果为浮点数
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值