0%

Leetcode4 寻找两个有序数组的中位数

题面描述 寻找两个有序数组的中位数,要求时间复杂度 $ O\left( \log_2 \left(n + m \right) \right)$

给定两个单调非减的有序数组,保证不全为空。求这两个数组合并后的中位数,要求时间复杂度 $ O\left( \log_2 \left(n + m \right) \right)$ 。

最朴素的想法肯定是二分套二分,考虑到第二个二分的size也会快速递减,时间复杂度应该在小常数 $ O \left( \log_2 n \cdot \log_2 m \right) $ 。

事实上这已经是非常优秀的复杂度了。如果面试官面我这个问题,我会说在普通电脑能储存存的数据规模下,上述复杂度和要求复杂度在计算时间上可能差异不大,在工程的考量上可以采用前者,因为前者实现更简洁不易产生bug。

正解没有想到,看了题解脑海里才冒出”k-th element”这个词(虽然不太贴切)。最近题目刷少了,思维码力都不行啊。

然后就是繁琐的实现过程,可能写得不是太好。

  • 考虑到两个有序数组求第 $k$ 大,分别取数组第 $\frac{k}{2}$ 个元素
    • 如果第一个数组的第 $\frac{k}{2}$ 个元素比较小,那表示第一个数组的前 $\frac{k}{2}$ 个元素都太小。抛弃这个部分,继续求剩下数组第 $\frac{k}{2}$ 小。
    • 反之亦然
  • 如果数组元素个数不够 $\frac{k}{2}$ 个,则比较最后一个值和另一个数组第 $\frac{k}{2}$ 个元素的大小
    • 如果最后一个值较小,那么直接从另一个数组里出答案
    • 否则,抛弃另一个数组前 $\frac{k}{2}$ 个元素,继续求剩下第 $\frac{k}{2}$ 小。

所谓抛弃一部分,只涉及指针位置的修改而非物理意义上的删除。那么每次问题规模折半,从分治角度出发这就是单分规模支折半问题,经典的对数复杂度。

考虑到初始 $ k= \frac{m+n}{2}$ 故总复杂度 $ O\left( \log_2 \left(n + m \right) \right)$ 。

另外还有各种细节:

  • 一个数组为空的时候要避免访问该数组
  • 给小规模问题一个出口总是好的(比如 $k=0$ 求第0小,那么一定是最小的两个数中小的那个)
  • 偶数个数的中位数问题(如果偷懒可以二分两遍,但一定有更好的做法)
1
class Solution {
2
protected:
3
    int n,m;
4
    int kth_elem(vector<int>& nums1,int L1,vector<int>& nums2,int L2,int k){
5
        if (L1==n) return nums2[L2+k];
6
        if (L2==m) return nums1[L1+k];
7
        if (k==0) return min(nums1[L1],nums2[L2]);
8
        int nk=(k-1)/2;
9
        if (n-L1<=nk){
10
            if (nums1[n-1]>=nums2[L2+nk]){
11
                return kth_elem(nums1,L1,nums2,L2+nk+1,k-nk-1);
12
            }
13
            return nums2[L2+k-(n-L1)];
14
        }
15
        if (m-L2<=nk){
16
            if (nums2[m-1]>=nums1[L1+nk]){
17
                return kth_elem(nums1,L1+nk+1,nums2,L2,k-nk-1);
18
            }
19
            return nums1[L1+k-(m-L2)];
20
        }
21
        if (nums1[L1+nk]>nums2[L2+nk]){
22
            return kth_elem(nums1,L1,nums2,L2+nk+1,k-nk-1);
23
        }
24
        return kth_elem(nums1,L1+nk+1,nums2,L2,k-nk-1);
25
    }
26
public:
27
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
28
        n=nums1.size(),m=nums2.size();
29
        double ans1=1.0*kth_elem(nums1,0,nums2,0,(n+m)/2);
30
        if ((n+m)%2==1) return ans1;
31
        double ans2=1.0*kth_elem(nums1,0,nums2,0,(n+m)/2-1);
32
        return (ans1+ans2)/2.0;
33
    }
34
};

Comment: 最近Leetcode推出每日一题的活动,3月1日开始,我准备坚持参加一下