leetcode4. 寻找两个正序数组的中位数
给定两个大小为 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
方法一:暴力法
思路:
首先考虑最简单的暴力法,没想到居然通过了。
直接将两个数组合并,排序,然后根据长度返回中位数即可。
排序可以写归并排序,这里直接调用sort函数了。
代码:
class Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:ans = nums1 + nums2ans.sort()n = len(ans)if n%2 == 0:return float((ans[n//2]+ans[(n//2)-1])/2)else:return float(ans[n//2])
结果:
方法二:二分查找递归
思路:
上面的时间复杂度不符合要求,这个要求的复杂度需要采用二分法。我们可以把这个问题转换为找两个数组中第k小的数,那么找中位数,根据总长度的奇偶,就是调用这个函数一次,或者两次来取平均的过程。
总体的思路是,我们首先判断是否有一个数组是空,如果是的话,那么很简单,只是对另一个数组找中位数即可;若不是,再进行下面过程,判断总长度奇偶。如果总长度为奇数,只要找到最中间的那个元素,即可,设为第k小的数;若为偶数,则找到中间两个数,第k小和第k+1小,再取平均即可。
下面介绍如何找到第k小的数&#xff0c;我们使用二分法&#xff0c;上下数组分别取第numm&#61;k//2个数&#xff0c;进行对比&#xff0c;若nums1[numm] <&#61; nums2[numm]&#xff0c;则说明nums1的前面这numm个数肯定小于目标数&#xff0c;抛弃&#xff0c;反之抛弃nums2的前面numm个数&#xff1b;下面问题就转换为在调整完&#xff08;抛弃掉没用的数&#xff09;之后的两个数组中&#xff0c;找第k-numm小的数的问题&#xff0c;就可以继续递归了。简单流程如下图所示。
此时numm&#61;&#61;2&#xff0c;两个数组的第二个元素&#xff0c;nums1的比较大&#xff0c;那么nums2的前numm个元素就抛弃掉&#xff0c;如下图黄色部分抛弃掉&#xff0c;然后问题就转换为剩下两个数组中找第2小的数。
问题转换为如图示问题。
由于2 <5&#xff0c;把nums1的前numm个元素删除掉&#xff0c;即把2删除掉&#xff0c;那么问题转换为。
由于此时就是找最小的数&#xff0c;不需要再二分&#xff0c;直接返回两个数组比较小的第一个数即可。这也是我们递归的一种边界条件&#xff0c;为了节约空间&#xff0c;我们不将数组传入递归函数&#xff0c;不对数组进行改变&#xff0c;只使用left1、right1、left2、right2表示nums1和nums2的搜索范围&#xff0c;t表示找第t的数。那么我们的递归终止条件有&#xff1a;
- 其中一个数组的left大于right了&#xff0c;说明这个数组在上一层已经全部被抛弃了&#xff08;上一层在调整边界时&#xff0c;左边界要加上抛弃的长度&#xff0c;全部抛弃的时候&#xff0c;得到的新的左边界&#xff0c;刚好超过右边界。&#xff09;&#xff0c;只要返回另一个数组的第t小数即可&#xff0c;即nums[left&#43;t-1]。
- 两个数组都还有数&#xff0c;t&#61;&#61;1&#xff0c;只要比较两个数组第一个元素&#xff0c;返回小的即可。
这里面还有一个小小的问题&#xff0c;我们在计算numm的时候&#xff0c;如果数组剩余的长度right-left&#43;1不足numm时候&#xff0c;我们就直接使用right的数来进行判断&#xff0c;因此表示参与抛弃的长度的时候&#xff0c;我们需要与现存数组长度进行判断&#xff0c;取小的。length1 &#61; min(numm,right1-left1&#43;1) 、length2 &#61; min(numm,right2-left2&#43;1)。
代码&#xff1a;
class Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:#找到nums1和nums2固定范围内的第t小的数&#xff0c;left1、right1、left2、right2为nums1和nums2的范围。def find(left1,right1,left2,right2,t): if left1 > right1: #此时其中一个数组已经无用&#xff0c;返回另一个数组的第t即可return nums2[left2&#43;t-1]elif left2 > right2: #此时其中一个数组已经无用&#xff0c;返回另一个数组的第t即可return nums1[left1&#43;t-1]if t &#61;&#61; 1: #两数组都存在的递归结束条件&#xff0c;找最小的一个数return min(nums1[left1],nums2[left2])numm &#61; t//2 #二分&#xff0c;向下取整&#xff0c;最多为t的一半#处理其中一个数组范围不足numm的情况&#xff0c;length表示判断之后丢弃的数的个数length1 &#61; min(numm,right1-left1&#43;1) length2 &#61; min(numm,right2-left2&#43;1)#判断left&#43;length-1的两个元素大小&#xff0c;调整两个数组的范围以及第几小元素&#xff0c;进行递归if nums1[left1&#43;length1-1] <&#61; nums2[left2&#43;length2-1]:return find(left1&#43;length1,right1,left2,right2,t-length1)else:return find(left1,right1,left2&#43;length2,right2,t-length2) m &#61; len(nums1)n &#61; len(nums2)#初始判断&#xff0c;处理其中有一个为空的情况if m &#61;&#61; 0:if n%2 &#61;&#61; 0:return float((nums2[n//2]&#43;nums2[(n//2)-1])/2)else:return float(nums2[n//2])if n &#61;&#61; 0:if m%2 &#61;&#61; 0:return float((nums1[m//2]&#43;nums1[(m//2)-1])/2)else:return float(nums1[m//2])ans &#61; 0.0#分两种情况讨论返回if (m &#43; n)%2 &#61;&#61; 1:k &#61; (m&#43;n&#43;1)//2ans &#61; float(find(0,m-1,0,n-1,k))else:k &#61; (m&#43;n)//2ans &#61; (float(find(0,m-1,0,n-1,k)) &#43; float(find(0,m-1,0,n-1,k&#43;1)))/2return ans
结果&#xff1a;