C++每日一题(找出两个个有序的数组的中位数)

题目:Median of Two Sorted Arrays
There are two sorted arrays A and B 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))

分析:

1.看到log(m+n)就要立马想到分治法

这里是两个有序的数组 ,所以两个有序的数组都给它变成固定的奇数个数 ,就是虚拟加入“#” 
注意是虚拟加 这样做起来就很方便了 ,所以两个数组总和就是2m+2n+2 
假设第一个是m个原来 第二个是n个 
这样两个有序数组合并以后的中位数就是m+n+1 
这有什么好处呢,为什么这么加?因为这么加完之后,每个位置可以通过/2得到原来元素的位置。 
 

2 .进一步总结:

让数组恒为奇数,有没有办法让两个数组长度相加一定为奇数或偶数呢?

其实有的,虚拟加入‘#’(这个trick在manacher算法中也有应用),让数组长度恒为奇数(2n+1恒为奇数)。 
Ps.注意是虚拟加,其实根本没这一步,因为通过下面的转换,我们可以保证虚拟加后每个元素跟原来的元素一一对应

之前        len     之后                      len
[1 4 7 9]    4    [# 1 # 4 # 7 # 9 #]    9
[2 3 5]       3    [# 2 # 3 # 5 #]          7
映射关系
这有什么好处呢,为什么这么加?因为这么加完之后,每个位置可以通过/2得到原来元素的位置。

/    原位置    新位置    除2后
0    1    0    1
5    2    5    2
在虚拟数组里表示“割”,不仅如此,割更容易,如果割在‘#’上等于割在2个元素之间,割在数字上等于把数字划到2个部分。

奇妙的是不管哪种情况:

Li = (Ci-1)/2 
Ri = Ci/2

例: 
1. 割在4/7之间‘#’,C = 4,L=(4-1)/2=1 ,R=4/2=2 
刚好是4和7的原来位置! 
2. 割在3上,C = 3,L=(3-1)/2=1,R=3/2 =1,刚好都是3的位置!

剩下的事情就好办了,把2个数组看做一个虚拟的数组A,目前有2m+2n+2个元素,割在m+n+1处,所以我们只需找到m+n+1位置的元素和m+n+2位置的元素就行了。 
左边:A[m+n+1] = Max(L1+L2) 
右边:A[m+n+2] = Min(R1+R2)

Mid = (A[m+n+1]+A[m+n+2])/2 
       = (Max(L1+L2) + Min(R1+R2) )/2

至于在两个数组里找割的方案,就是上面的方案。

分治的思路
有了上面的知识后,现在的问题就是如何利用分治的思想。

怎么分?
最快的分的方案是二分,有2个数组,我们对哪个做二分呢? 
根据之前的分析,我们知道了,只要C1或C2确定,另外一个也就确定了。这里,为了效率,我们肯定是选长度较短的做二分,假设为C1。

   
L1 R1
L2 R2

怎么治?
也比较简单,我们之前分析了:就是比较L1,L2和R1,R2。 
- L1>R2,把C1减小,C2增大。—> C1向左二分 
- L2>R1,把C1增大,C2减小。—> C1向右二分

越界问题
如果C1或C2已经到头了怎么办? 
这种情况出现在:如果有个数组完全小于或大于中值。可能有4种情况: 
- C1 = 0 —— 数组1整体都比中值大,L1=nums1((c1-1)/2)
- C2 = 0 —— 数组1整体都比中值小,L2-nums2((c2-1)/2)

- C1=2*n-----数组1整体都比中值小,R1=nums1(c1/2)

- C2=2*n-----数组2整体都比中值小,  R2=nums2(c2/2);

测试代码:

#include 
#include 
#include 
#include 
using namespace  std;

/*
 *找出两个有序数数组的中位数
*/
class leet_04{
public:

double findMediaSortedArray(const vector& nums1,const vector&nums2)
{
    int n=nums1.size();
    int m=nums2.size();
    if(n>m)//确保数组长度最短的进行二分。递归
        return findMediaSortedArray(nums2,nums1);
    int L1,L2,R1,R2,c1,c2,left=0,right=2*n;//虚拟的加上#号后变成2n+1,但是数组的下标是从零开始的
    while(left<=right) //采用二分法
    {
      c1=(left+right)/2;//c1是二分法的结果
      c2=m+n-c1;//k=n+m.确保数组左右加起来等于k
      L1=(c1==0)?INT_MIN:nums1[(c1-1)/2];
      R1=(c1==2*n)?INT_MAX:nums1[c1/2];
      L2=(c2==0)?INT_MIN:nums2[(c2-1)/2];
      R2=(c2==2*m)?INT_MAX:nums2[(c2/2)];
      if(L1>R2)
          right=c1-1;
      else if(L2>R1)
          left=c1+1;
      else
          break;
    }
    return ((max(L1,L2)+min(R1,R2))/2.0);
 }
};
//test
int main()
{
  int arr1[]={1,2};
  int arr2[]={3,4};
  vector nums1(arr1,arr1+sizeof(arr1)/sizeof(int));
  vector nums2(arr2,arr2+sizeof(arr2)/sizeof(int));
  leet_04 test;
  double mediaval=test.findMediaSortedArray(nums1,nums2);
  cout<


 

你可能感兴趣的:(笔试刷题每日一题)