欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Leetcode-Median of Two Sorted Arrays

发布时间:2024/4/11 编程问答 30 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Leetcode-Median of Two Sorted Arrays 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

    • 前言
    • 题目分析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);elsereturn a[i-1]; }double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {int total = nums1Size+nums2Size;if (total & 1) //oddreturn getKth(nums1, nums1Size, nums2, nums2Size, total/2+1);else //evenreturn (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);} elsereturn *(it1+i-1); }double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {int total = nums1.size()+nums2.size(); if (total & 1) //oddreturn getKthOfVectors(nums1, nums1.begin(), nums2, nums2.begin(), (total+1)/2);else //evenreturn (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];elsenum1 = max(nums1[i-1], nums2[j-1]);if ((sz1+sz2) & 1) //oddreturn 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个已经排序的一维数组有比较好的效果。

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

总结

以上是生活随笔为你收集整理的Leetcode-Median of Two Sorted Arrays的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。