Median of two sorted Array 寻找两个有序数组的中位数
这一道题是leet code 上面的一道Hard problem,其主要的难度在于其要求在时间复杂度为
O
(
l
o
g
(
m
+
n
)
)
O(log(m+n))
O(log(m+n)) 的情况下完成。
一般当我们看到这个题的时候第一反应就是用Merge two sorted array的方法去完成,但是这样的话时间复杂度就无法满足要求,因为merge two sorted array需要
O
(
m
+
n
)
O(m+n)
O(m+n)的时间。那如何才能在
O
(
l
o
g
(
m
+
n
)
)
O(log(m+n))
O(log(m+n))的时间复杂度内完成这个寻找中位数的要求呢,其实当我们看到这个log的时候就明白应该要使用二分法相关的做法。不过这个二分法较为特殊,需要结合我们上一篇blog里面提到的quickselect算法一起使用。
问题拆解
怎么变成二分法?
假设数组1的长度为m, 数组2的长度为n。
我们要理解这个问题的本质,寻找两个有序数组的中位数,就是寻找两个数组合并之后的第k的数,(基数偶数先不讨论)而这个第k个数就是(m+n)/2。所以我们可以理解为使用quickselet算法快速选取第K个数。
那如何在两个数组中使用类似二分法的方式去寻找共同的第K个数呢?
最理想的方式就是我们可以一次性的排除当前数组中接近一半的错误答案,这样非常符合二分法的需求。
可是怎么实现这个一次性舍去一半错误答案的机制呢。我们可以采用两个指针分别指向两个两个数组的头,然后不断向数组的末尾移动,这样指针移动过的元素则是被我们舍去的不存在正确答案的部分。
该丢哪一边呢?
指针的移动一次只能移动其中一个数组的指针,我们应该如何选择移动哪一个数组的指针呢?
假设我们需要寻找到第K的数,我们先取出两个数组的第K/2个数进行对比。
val1 = nums1[k/2-1]
val2 = nums2[k/2-1]
我们对比val1 和val2,较小的那一边显然是不存在我们需要的答案的。这一点可能有一些难以理解,我们可以利用merge two sorted array的方式来理解为什么要进行这样的操作。如果val1小于val2时,当我们merge 出的新数组拥有k个元素的时候,数组一的前k/2个数肯定都已经被merge出来了,所以我们对于这一段不含有正确答案的元素就直接弃用,利用指针跳过了。
注意这里我们之所以这么做,并不是为了追求速度,而是弃用哪一段最为保险,肯定不会拥有正确答案,这样去理解就能明白为什么要这样做了。
当我们不断的移动指针,会出现一些情况,比如说某一个数组已经全部被丢弃了,那我们就只需要对另一个不为空的数组进行操作,或者是当k==1的时候,我们要找的就是第一个数了就可以直接返回了。
让我们看一下具体的java实现吧。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int total = nums1.length + nums2.length;
if(total %2 == 0){
// 出现了长度为偶数的情况
return (
findKth(nums1,0,nums2,0,total/2)+
findKth(nums1,0,nums2,0,total/2+1)
)/2.0;
}else{
//长度为奇数
return findKth(nums1,0,nums2,0,total/2+1);
}
}
private int findKth(int[] nums1, int i, int[] nums2, int j, int k){
//当第一个数组已经为空了
if(i >= nums1.length){
return nums2[j+k-1];
}
//第二个数组已经为空了
if(j >= nums2.length){
return nums1[i+k-1];
}
// 寻找当前剩余数组元素内的最小的(k==1)
if(k ==1){
return Math.min(nums1[i],nums2[j]);
}
int key1 = Integer.MAX_VALUE;
int key2 = Integer.MAX_VALUE;
//取得各自数组中的k/2个元素
if(i+k/2-1 < nums1.length){
key1 = nums1[i+k/2-1];
}
if(j+k/2-1 < nums2.length){
key2 = nums2[j+k/2-1];
}
//进行比较决定丢弃哪一个数组的前k/2个元素。
if(key1 < key2){
return findKth(nums1,i+k/2,nums2,j,k-k/2);
}else{
return findKth(nums1,i,nums2,j+k/2,k-k/2);
}
}
}
这样拆解就可以成功的将这道hard problem利用二分法和quickselet的思想在 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)) 的时间复杂度内解决了。