Leetcode-Median of Two Sorted Arrays

来源:互联网 发布:淘宝链接地址怎么弄 编辑:程序博客网 时间:2024/06/05 16:00

    • 前言
    • 题目分析1
      • 算法的正确性
      • 代码
      • 效率
    • 题目分析2
      • 算法的正确性
      • 代码
      • 效率
    • 推广
      • 结语

前言

Leetcode刷到150道了,各种题型都已经练习了一遍,没有必要再去刷数量了!分类总结解题方法,完善知识体系已经是刻不容缓了。尤其是在看了《暗时间》之后,深有感触。总结、反思自己的思维过程也许是最重要的。对每道题进行深加工,抽象出一般的概念,得到一般的解题策略。这个过程才是最重要的,是沉淀思想的绝好途径。

先简单摘一些常用的解题方法,以后每碰到难题的时候,都要想一下用这些方法是否可以解决:

  • 时刻不忘未知量
    时刻要想到自己的问题是什么,要求什么。

  • 用特例启发思考
    构造一个合适的实例,可能会发现一般的规律。

  • 反过来推导
    设立未知数,从结论出发,向已知条件靠扰。

  • 试错

  • 调整题目的条件
    去掉一个条件,观察区别,再放上那个条件,感觉到题目的内在结构上的某种约束,进而得到答案。
  • 求解一个类似的题目
    为了优化脑中的知识结构,我们在记忆掌握和分析问题的时候都应该尽量抽象地去看待,这样才能建立知识的本质联系。
  • 列出所有可能与题目有关的定理或性质
    比如这道题目,可以列出这样的性质:中位数是数组中最中间的数。如果元素总数为奇数,它左边所有元素的个数和右边所有元素的个数相等;如果为偶数,则将所有元素平分成两左右两部分,两部分元素个数相等, 中位数为最中间两者的均值。
  • 考察反面,考察其他所有情况
  • 将问题泛化
    这道题应该要进行泛化,比如如果要求两个排序元素里的第K大元素怎么求?如果是n个排序数组呢?

题目分析1

Leetcode-CPP_p14可以从结论来推导方法:题目要求用log(m+n)的复杂度,而中位数的序号为i=m+n2,要想达到要求的复杂度,则每次查找都应该使i减半,即要用到二分搜索。那怎样才能用到二分搜索呢?这一步还不是那么明显,答案是将原问题泛化,寻找两个排序数组的第k小的数,然后每步排除k/2个数,则最后可以达到复杂度要求。

假设A,B两个数组的元素个数都大于k/2,那么将A,B的第k个元素,也就是A[k21]B[k21]作比较的话,

A[0]B[0]A[1]B[1]A[k21]B[k21]A[k2]B[k2]

可以得到:

A[k21]<B[k21],A[k21]>B[k21],A[k21]==B[k21],(1)(2)(3)

对于情形(1)A[0]A[k21]一定是排在B[k21]之前,因此A[0]A[k21]绝对不会是第k小的数,可以在下一轮寻找中去掉,因此下一轮比较将变成:

A[k2]B[0]B[1]A[k2+k41]B[k41]B[k4]

这里我们不能排除B中的元素,是因为我们仅仅知道A[k21]B[k21]的大小关系,而不知道A[k21]B[0]的关系,B[0]可以很大,以至于大于A[k21],而这个时候,其实我们是得不出进一步的结论的,因为我们还是不知道B[0]A[k21]后面元素的关系。当然,另一方面,B[0]可以比较小,以至于当它增加到B[k21]时只比A[k21]大了一点点,且小于A[k2],那么B[k21]就是我们要找的第k小的数,只是找到它还需要再递归几次。而这并不难分析。

因为我们是要寻找第k小的数,永远不要忘了我们的目的是什么——走得太远,不要忘了当初是为什么出发!而我们已经排除了k2个数,因此下一步是寻找第k2(这里的第k2是指包括当前元素的元素个数)小的数,因而又可以排除一半的数,即k4

情形(2)的分析类似;

而情形(3)就更简单了,直接可以得到要找的数就是A[k21]。因为一定可以得到下面的排列:

A[0]A[k22]B[0]B[k22] A[k21] B[k21]

虽然我们不知道AB中的前k2个数的具体顺序,但是最后两个数一定是A[k21],B[k21],而最后一个数正是我们要找的第k小的数。

算法的正确性

如何证明算法的正确性呢?
每次递归都会排除一半的元素或者排除掉整个数组,即当k2>m,最后一定会得到正确的结果。

代码

int getKth(int a[], int m, int b[], int n, int k){    if (m > n)        return getKth(b, n, a, m, k);    if (0 == m)        return b[k-1];    if (1 == k)        return min(a[0], b[0]);    int i = min((k+1)/2, m);    /*if (a[i-1] < b[i-1])        return getKth(a+i, m-i, b, n, k-i);    else if (a[i-1] > b[i-1])        return getKth(a, m, b+i, n-i, k-i);*/    int j = k-i;    if (a[i-1] < b[j-1])        return getKth(a+i, m-i, b, n, k-i);    else if (a[i-1] > b[j-1])        return getKth(a, m, b+i, n-i, k-j);    else        return a[i-1];}double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size){    int total = nums1Size+nums2Size;    if (total & 1) //odd        return getKth(nums1, nums1Size, nums2, nums2Size, total/2+1);    else //even        return (getKth(nums1, nums1Size, nums2, nums2Size, total/2+1) + getKth(nums1, nums1Size, nums2, nums2Size, total/2))/2.0;       }

代码中注释的地方是有问题的,如果只是比较A[i1]B[i1],那么无论i是等于k2还是等于k+12,最后都是不能直接用后面三种情况来处理的。所以我们还需要一个变量j=ki来保证目前我们比较的元素个数为k。还是那句话,不要忘了最初的目的是什么。

所以这里的关键在于选出k个数,比较每个一维数组的最后一个元素的大小。对于kn大于一维数组的长度m的情形,就会越界,这时只能取m个元素了,那另外一个数组就必须取km个元素了,对于n==2时,显然km对于第2个数组是不越界的。但对n>2的情形,则情况会复杂很多。

下面是用vector加上迭代器的代码:

int getKthOfVectors(vector<int>& nums1, vector<int>::iterator it1, vector<int>& nums2, vector<int>::iterator it2, int k){    int sz1 = nums1.end()-it1;    int sz2 = nums2.end()-it2;    if (sz1 > sz2)        return getKthOfVectors(nums2, it2, nums1, it1, k);    if (0 == sz1)        return *(it2+k-1);    if (1 == k)        return min(*it1, *it2);    int i = min((k+1)/2, sz1);    int j = k-i;    if (*(it1+i-1) < *(it2+j-1))    {        it1 += i;        return getKthOfVectors(nums1, it1, nums2, it2, k-i);    }       else if (*(it1+i-1) > *(it2+j-1))    {        it2 += j;        return getKthOfVectors(nums1, it1, nums2, it2, k-j);    }       else        return *(it1+i-1);}double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2){    int total = nums1.size()+nums2.size();      if (total & 1) //odd        return getKthOfVectors(nums1, nums1.begin(), nums2, nums2.begin(), (total+1)/2);    else //even        return (getKthOfVectors(nums1, nums1.begin(), nums2, nums2.begin(), total/2+1)+getKthOfVectors(nums1, nums1.begin(), nums2, nums2.begin(), total/2))/2.0;}

效率

假定n是要找的第n小的数。则:
T(n)=T(n/2)+O(1),由主定理T(n)=logn

题目分析2

根据discuss里分享的解答,还可以利用中位数的这一性质:中位数两边的元素个数相等(或相差1)。列出这一性质并不难,难就难在怎么根据这一性质继续往下走。

A[0]B[0]left partA[1]B[1]A[i1]B[j1]right partA[i]B[j]A[m1]B[n1]

当左右两部分的元素个数相等或者相差1时,而且A[i]>B[j1],B[j]>A[j1],那么中位数就不难找出来了。因此我们只要找出i

由此,可列方程i+j=mi+nj或者i+j=mi+nj+1

{i+j=mi+nj,i+j=mi+nj+1,(m+n)(m+n)

{j=m+n2i,j=m+n+12i,(m+n)(m+n)

j=m+n+12i(将m+n为奇数和偶数统一起来)

因此我们只要在0~m中寻找i,就可以得到解答,而且由于是寻找中位数,它一定是存大的,就是说我们用binary search来寻找i,是一定能找到的。值得注意的是,这里的i[0,m],当i==0时,A全部在left part,当i==m时,A全部在rigth part部分。

算法的正确性

每次查找,要么找到i,要么会缩小查找范围,而i一定是存在的,所以最后一定能找到i

代码

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2){    int sz1 = nums1.size();    int sz2 = nums2.size();    if (sz1 > sz2)        return findMedianSortedArrays(nums2, nums1);    int imin = 0;    int imax = sz1;    int i, j;    while (imin <= imax)    {        i = (imin+imax)/2;        j = (sz1+sz2+1)/2 - i;        if (i > 0 && j < sz2 && nums2[j] < nums1[i-1])            imax = i-1;        else if (j > 0 && i < sz1 && nums1[i] < nums2[j-1])            imin = i+1;        else             break;    }    int num1;    if (0 == i)        num1 = nums2[j-1];    else if (0 == j)        num1 = nums1[i-1];    else        num1 = max(nums1[i-1], nums2[j-1]);    if ((sz1+sz2) & 1) //odd        return num1;    int num2 = min(nums1[i], nums2[j]);    return (num1+num2)/2.0;}

注意:代码最后返回时用到除法,除数要用2.0,否则返回的是int类型转换到double,结果错误。

效率

二分查找的效率当然是log2min(m,n)

推广

如果是在n个已排序的数组,寻找第k小的数,该怎么求呢?
根据思路1,我们可以比较每个数组的第kn个数,如果全部相等,则找到第k小的数;否则,可以排除(n1)kn个数,只有最大的那个元素所在的数组不能排除掉。

上面所说的是理想情况下,实际写代码的时候要考虑的东西稍复杂一些,当数组的元素个数小于kn时,明显就会越界。再有首先得保证,所有数组的元素总数一定大于k的。

因此,可以推广为找出二维vector中的第k小的数。

接口为:

double findMedianSortedArrays(vector<vector<int>> &nums, int k)

效率又该怎么计算呢?
这里nk均有可能是变量,为了更好的与主定理对应,我们用
n表示输入的规模,即

(1)如果是在n个已排序的数组,寻找第b小的数

复杂度与n其实没有关系,只与b有关,因此T(n)=O(1)

(2)如果是在b个已排序的数组,寻找第n小的数

每次递归后n都会变成原来的1bT(n)=T(n/b)+O(1),由主定理T(n)=logn
最坏情况下,每次只能排除一个数,那么时间复杂度就会降为O(n)

结语

如果n维数组的每维的长度相等,还比较好办。如果每维的长度不等,对于最好情况下的每次递归后规模变成原来的1n就会退化成每次递归后规模只减了1。所以,以上的思路只对2个已经排序的一维数组有比较好的效果。

不管怎么说,第一篇博客,加油!!!

0 0
原创粉丝点击