[leetcode] 4 寻找两个有序数组的中位数(二分+递归查找第K小数)(重要)

问题描述

给定两个大小为 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

解题思路

看这个时间复杂度,应该是要进行二分求解,题意没有说清,应该是两个有序数组放在一起排序后的中位数。我们可以将其变得更加通用一些,转化为求两个有序数组的第K小数,要求时间复杂度O(log(m+n))。官方题解是二分求中位数,我们这里用递归来讲一下求第K小数。这里用的是二分的思想,但是是递归的模版。

假设第一个数组是a数组,长度是n,第二个数组是b数组,长度是m。假设下标从0开始,设 x = K 2 1 x=\frac{K}{2}-1 ,我们先从第一个数组中找到第 K 2 \frac{K}{2} 个数为 a x a_x ,第二个数组中找到第 K 2 \frac{K}{2} 个数为 b x b_x 。然后比较这两个数,如果 a x < b x a_x<b_x ,说明第K个数位于a数组的第 K 2 \frac{K}{2} 个数的后半段,或者b数组中。

比如我们考虑两个数组:

a[1, 3, 6, 7]
b[2, 4, 8, 10, 13]

我们要找到的中位数是6,是第5个数, x = 5 2 1 x=\frac{5}{2}-1 ,首先第一步我们找到 a x = 3 , b x = 4 a_x=3, b_x=4 ,经过比较,发现 a x < b x a_x<b_x ,所以我们剩下的数组是(将小的那个a_x也扔掉):

a[6, 7]
b[2, 4, 8, 10, 13]

然后在这个里面搜第 K K 2 K- \frac{K}{2} 大元素,也就是第 3 3 大元素,此时 x = 3 2 1 x=\frac{3}{2}-1 ,找到 a x = 6 b x = 2 a_x=6,b_x=2 ,比较发现 a x > b x a_x>b_x ,所以我们剩下的数组是:

a[6, 7]
b[4, 8, 10, 13]

然后搜第 3 3 / 2 = 2 3-3/2=2 大元素,此时 x = 2 2 1 x=\frac{2}{2}-1 ,找到 a x = 6 , b x = 4 a_x=6, b_x=4 ,比较发现 a x > b x a_x>b_x ,所以还剩下的是:

a[6, 7]
b[8, 10, 13]

此时应该找第1个元素,也就是两个数组首元素中最小的一个。因此最终答案就是6。

那为什么这样削呢?我们看下面这个图:
在这里插入图片描述
a和b是原数组,c是合并之后的数组,蓝线表示 a x a_x ,红线表示 b x b_x ,那么我们看c中,蓝线前面必定少于K个元素(因为 a x < b x a_x<b_x ,同时这两个都是第 K 2 \frac{K}{2} 个元素,所以二者前面(包含二者)总共有K个元素(如果K为奇数就是K-1个元素)),所以说前面这部分就可以扔掉,直接去除了 K 2 \frac{K}{2} 个数据。

还要考虑一种情况,就是你拿不到第 K 2 \frac{K}{2} 个元素,就是某个数组太短了。这个时候我们就直接取最短的那个数组的最后一个元素,然后进行比较。如果这个更小,那么就将整个数组去掉,同时K-size()。

由于我们每次都是删除掉 K 2 \frac{K}{2} 个元素,而 K = n + m 2 K=\frac{n+m}{2} ,所以时间复杂度是O(log(m+n))的。同时空间复杂度也非常优秀,虽然我们用到了递归,但是这个递归属于尾递归,所以编译器不需要不停地堆栈,空间复杂度为 O(1)。

参考博客 https://leetcode.wang

完整代码

class Solution {
public:
    int getKth(vector<int>& nums1, int start1, vector<int>& nums2, int start2, int k){
	    int len1=nums1.size()-start1,len2=nums2.size()-start2;
	    if(len1==0) return nums2[start2+k-1];//某个数组为空,直接返回另一个数组的第k个元素
	    if(len2==0) return nums1[start1+k-1];
	    if(k==1) return min(nums1[start1],nums2[start2]);//找第1个元素,直接找两个数组首个元素的最小值
	    int k1=start1+min(len1,k/2)-1,k2=start2+min(len2,k/2)-1;//找到两个需要断的地方
	    return nums1[k1]<nums2[k2] ? getKth(nums1,k1+1,nums2,start2,k-(k1-start1+1)):getKth(nums1,start1,nums2,k2+1,k-(k2-start2+1));
	}
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2){
	    int n=nums1.size(),m=nums2.size();
	    if((n+m)%2) return getKth(nums1,0,nums2,0,(n+m+1)/2);//如果是奇数,找到中位数
	    else return (getKth(nums1,0,nums2,0,(n+m)/2)+getKth(nums1,0,nums2,0,(n+m+2)/2))*0.5;//偶数找到中间两个平均值
	}
};
原创文章 61 获赞 58 访问量 6475

猜你喜欢

转载自blog.csdn.net/weixin_43347376/article/details/105808761
今日推荐