LeetCode #4 Median of Two Sorted Arrays 找中位数 二分

Description


 

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)). 

给定两个排序好的数组,它们的大小分别是 m 和 n,找出这两个数组的中位数,要求整体运行时间复杂度为 O(log(m + n))。

 

 example:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0
nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

 

思路


 

  一看到给定的数组是已排序的和时间复杂度要求为 O(log(m + n)) ,就立马联想到用二分查找、归并,我先用归并做了这道题,虽然时间复杂度是 O(n),但是经过优化后的算法执行时间也还是不错 。

  暴力归并的思路很简单,开一个辅助空间 C,利用索引 i1 与 i2 对 num1[i1] 与 num2[i2] 进行比较,并将最小的值存入 C 中,最后奇偶情况输出 C 中的中位数即可。

  我们可以对归并算法进行空间上的优化,由于 C 的中位数位于表长一半的位置,那么表长的后半部分其实可以不用存。而且,由于本题的解只需要返回一个 double 型的中位数,我们不需要真的去创建一个数组而是用 res1 res2,当 C 的索引到达中间的位置时,存储 C 最后的两个元素并根据奇偶情况输出即可,这样可以减少空间的开销。  

 

//Runtime: 52 ms
#include<algorithm>
using std::min;
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.empty() && nums2.empty()) {
            return 0;
        }
        int l1 = nums1.size(), l2 = nums2.size();
        int l3 = l1 + l2;
        int i1 = 0, i2 = 0, i3 = 0;
        int indexM1 = (l3-1)/2, indexM2 = l3/2; //the former for odd,the latter for even
        double res1 = 0, res2 = 0;
        //mergeSort
        while (i1 < l1 && i2 < l2) { 
            if (i3 > indexM2) {
                break;
            }
            if (i3 == indexM1) {
                res1 = min(nums1[i1],nums2[i2]);
            }
            if (i3 == indexM2) {
                res2 = min(nums1[i1],nums2[i2]);
            }
            if (nums1[i1] < nums2[i2]) {
                i1++;
            }
            else {
                i2++;
            }
            i3++;
        }
        
        while (i1 < l1) { //nums1 has element
            if (i3 > indexM2) {
                break;
            }
            if (i3 == indexM1) {
                res1 = nums1[i1];
            }
            if (i3 == indexM2) {
                res2 = nums1[i1];
            }
            i1++;
            i3++;  
        }
        while (i2 < l2) { //nums2 has element
            if (i3 > indexM2) {
                break;
            }
            if (i3 == indexM1) {
                   res1 = nums2[i2];
            }
            if (i3 == indexM2) {
                res2 = nums2[i2];
            }
            i2++;
            i3++;
        }
        
        //for odd,return res1;for even,return (res1 + res2)/2.0;
        if (l3%2 == 0) {
            return (res1 + res2)/2.0;
        }
        else {
            return res1;
        }
    }
};

 

  但是归并算法的时间复杂度本质上还是 O(n) ,达不到本题的要求。要想进一步优化使时间复杂度有指数级的变化,只能改变策略,这里使用了一种“伪”二分查找,时间复杂度是 O(lg(n)^2) 。

  首先,中位数的实质上是第 k 小的数,此时 k = (m+n)/2 ,若为偶数情况则则找出第 k+1 小的数并取平均值就好了。

  我们设两个数组元素个数都大于 k/2 ,我们取出 A[k/2 - 1] 与 B[k/2 -1] ,它们分别表示 A 的第 K/2 小的元素与 B 的第 K/2 小的元素,若 A[k/2 - 1] <= B[k/2 -1] ,说明 A[0] 到 A[k/2 - 1] 在合并后的序列里都在第 k 小的数之前,也就是说 A[0...k/2-1] 不可能会大于第 k 小值,因此可以将其舍弃。

  同理,对于 A[k/2 - 1] <= B[k/2 -1] 也存在类似的结论。

  讨论一下这个算法的时间复杂度,由于每次处理的仅是一个数组的前 k/2 个元素,对于长度为 m 的数组,它的时间复杂度为 O(lg(m)) ,对于长度为 n 的数组,它的时间复杂度为 O(lg(n)) ,所以它的时间复杂度为 O(lg(m) * lg(n)) ,由于它并不是在整体意义上进行的二分查找,所以达不到 O(lgn)。

 

//Runtime: 59 ms
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        if ((m+n)%2 == 0) {
            return (findKth (nums1, nums2, (m+n)/2) + findKth (nums1, nums2, (m+n)/2+1)) / 2.0;       
        }else { 
            return findKth (nums1, nums2, (m+n)/2 + 1);
        }
    }
private:    
    int findKth(const vector<int>& a, const vector<int>& b, int k) {
        int m = a.size(), n = b.size();
        if (m > n) {
            return findKth(b, a, k);
        }
        if (m == 0) {
            return b[k-1];
        }
        if (k == 1) {
            return std::min(a[k-1], b[k-1]);
        }
        int i = std::min(k/2, m), j = std::min(k/2, n);
        if (a[i-1] < b[j-1]) {            
            return findKth (vector<int>(a.begin()+i, a.end()), b, k-i);
        }else{
            return findKth (a, vector<int>(b.begin()+j,b.end()), k-j);
        }
    }   
};

 

  有人利用了其他的方法,实现了整体意义上的二分查找,时间复杂度为 O(lg(m+n)) ,十分巧妙,由于我没有去实现,所以这里只给出方法的链接,共大家参考讨论

posted @ 2017-12-29 14:40  bw98  阅读(246)  评论(1编辑  收藏  举报