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

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

题目描述

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

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

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

left_of_nums1\bf{left\_of\_nums1}left_of_nums1right_of_nums1\bf{right\_of\_nums1}right_of_nums1
i=0i=0i=0Nonenums1
0&lt;i&lt;m0&lt;i&lt;m0<i<mnums1[0],nums1[1],…,nums1[i-1]nums1[i],nums1[i+1],…,nums2[m-1]
i=mi = mi=mnums1None

同理,将nums2分成两个部分,左边jjj个元素,右边n−in-ini个元素:

left_of_nums2\bf{left\_of\_nums2}left_of_nums2right_of_nums2\bf{right\_of\_nums2}right_of_nums2
j=0j=0j=0Nonenums2
0&lt;j&lt;m0&lt;j&lt;m0<j<mnums2[0],nums2[1],…,nums2[j-1]nums2[j],nums2[j+1],…,nums2[n-1]
j=mj = mj=mnums2None

left_of_nums1\bf{left\_of\_nums1}left_of_nums1left_of_nums2\bf{left\_of\_nums2}left_of_nums2放入集合left\bf \blue {left}leftright_of_nums1\bf{right\_of\_nums1}right_of_nums1right_of_nums2\bf{right\_of\_nums2}right_of_nums2放入集合right\bf \blue {right}right如果我们可以确认:
        ① len(left)=len(right) 或者len(left) =  len(right)+1len(\blue {left}) = len( \blue {right}) \ 或者len(\blue {left})\ =\ \ len( \blue {right})+1len(left)=len(right) len(left) =  len(right)+1
        ② max(left)≤min(right)max(\blue {left}) \le min( \blue {right})max(left)min(right)

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

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

结合之前所说,可以这么做:
        把上面要满足的条件②放到else语句里,这样,可以把特殊情况的解答也放在else里,减少代码冗余;而if语句用来调整i_mini\_mini_mini_maxi\_maxi_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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值