最近在努力做题~~
昨天做到一道题没感觉还是挺有难度的……我看了半天终于把题解搞懂了,所以写一篇博客来整理一下~~~
题目描述
这道题来源于力扣题库中的腾讯精选50题。
题目描述如下:
可以注意到题目难度为困难。
虽然评论区有不少大佬吐槽这题目都算困难??
作为小白直觉自己太菜了……不过没关系,等我们把它搞懂了,这样的题目在我们这儿也就不是困难啦~~~
在我们开动脑筋解决困难之前,先活动活动手指点个一键三连吧~~~~
从题目描述和给出的示例,我们可以大致了解题意是让我们将两个按照正序(升序)排好的数组合并成一个有序的数组,并返回数组的中位数。
题目分析
什么是中位数?
根据释义,中位数就是一组数据的中间值:
当这组数的个数为奇数时,中位数就是数组中间的值。
当这组数的个数为偶数时,中位数是数组中间两个数的平均数。
所以,这里我们要注意偶数和奇数两种情况的讨论。
从例子中返回的结果以及给出的代码可以知道方法的返回值为double。
这道题按照基本思路应该是将两个数组合并起来,然后再找中位数。
但是……
题目要求时间复杂度为O(log(m+n))
大概就是这一把操作让题目的难度瞬间达到了“困难”的等级……
不过,别慌……
虽然题目要求了时间复杂度,但是从log(m+n)这个时间复杂度看,它反而给了我们一些提示:
你们快看!!!我log()摆明就是要你往二分法看齐的啦!
所以传统的方法1(先进行归并,再找中位数)和方法二(假装归并了再找中位数)都不能达到这个时间复杂度。
(方法一和方法二作为附加解法在后面给出)
其实找中位数的本质就是找数!
那么找数的本质是什么呢?
就是排除不符合的呀!!
那怎么找才能更快呢??
当然是一次性排除的越多越好啦!!!
那么结合二分法,我们如果一次就能能排除一半就好啦!
虽然实际上我们并不能一次排除一半(天底下哪有那么香的事情呢?)
但是事情似乎有那么一点儿端倪了……
下面先说两个思路:
思路一:这里的中位数,本质上就是排在中间的数,其实就是找第K大的问题。
思路二:根据中位数两边的性质,中位数左右两边的数的个数相等,我们可以将数组从中间进行分割,保证左边的数都小于右边的数,这样我们就可以在分割线处找到中位数。
以上两种思路分别对应下面的解法一和解法二。
解法一 找第K大问题
首先看解法一,这里K是什么?
int m = nums1.length;
int n = nums2.length;
int len = m + n;//两个数组的总长度
当len为奇数时,K = len / 2 + 1;
当len为偶数时,我们应该计算中间两个数的平均值,中间两个数分别为Kleft = len / 2, Kright = len / 2 + 1;
这里我们希望将奇数和偶数进行合并,可以将奇数的中位数变成两个相等的数,即K = Kleft = Kright。
由于整数的除法是向下取整:
- len = 奇数时,以5为例,我们要找的是第三个数:
5>>1 = 2;
(5+1)>>1 = 3;
(5+2)>>1 = 3;
- len=偶数时,以6为例,我们要找的是第3和第4个数:
6>>1 = 3;
(6+1)>>1 = 3;
(6+2)>>1 = 4;
所以,我们可以这样合并两种情况:
Kleft = (len+1)>>1;
Kright = (len+2)>>1;
那么接下来的问题就是:
如果使用二分法找第K个数应该怎么找呢?
大致思路是:先找两个数组中的第K/2个数,将两数进行比较,由于数组是有序的,所以较小的第K/2个数一定不是符合要求的数,我们可以排除出去,然后再在剩下的数中找第K-K/2个数。
具体解法看下图:
由此我们可以得到:
通过排除前面的K/2个数,在剩下的新数组中找第K-K/2个数,如此递归,当K=1时递归结束,返回当前数组的最小值。
这样我们就可以分别找到Kleft和Kright的值,计算题目之间的平均值,就是我们要找的中位数。
相信到这里你已经知道怎么解题啦!
上代码!!!
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int left = (m+n+1)>>1;
int right = (m+n+2)>>1;
return (findkth(nums1,0,nums2,0