LeetCode 4. Median of Two Sorted Arrays

[Chinese ver]

4. Median of Two Sorted Arrays

這里有兩個(gè)有序數(shù)組nums1和nums2,他們各自的大小為m和n.
找到這兩個(gè)數(shù)組的中間值浩聋,總的時(shí)間復(fù)雜度應(yīng)該為O(log (m+n)).

Example 1:
nums1 = [1, 3]
nums2 = [2]
 中間值是 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
中間值是 (2 + 3)/2 = 2.5

方法一:

首先嘗試了一種比較笨的方法

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //需要幾個(gè)數(shù)值
        int needNum = 0;
        //數(shù)值位置
        int needIndex = 0;
        //中間值
        double median = 0;
        int big = 0;
        int stand = 0;
        //合成的數(shù)組
        int[] insertnum ;

        int m = nums1.length;
        int n = nums2.length;

        if((m+n)%2==0){
            needNum = 2;
            needIndex = (m+n)/2-1;
        }else{
            needNum=1;
            needIndex = (m+n)/2;
        }
        insertnum = new int[m+n];
        //nums1的下標(biāo)
        int j =0;
        //nums2的下標(biāo)
        int k = 0;
        for(int i =0;i<m+n;i++){
            if(k==n){
                insertnum[i] = nums1[j];
                j++;
            }
            else if(j==m){
                insertnum[i] = nums2[k];
                k++;
            }else if(nums1[j]>nums2[k]){
                insertnum[i] = nums2[k];
                k++;
            }else{
                insertnum[i] = nums1[j];
                j++;
            }
        }
        if(needNum == 1){
            median = Double.valueOf(insertnum[needIndex]);
        }else{
            median = Double.valueOf(insertnum[needIndex]+insertnum[needIndex+1]) /2.0;
        }

        return median;

    }
}

順便提一下凶赁,這個(gè)方法經(jīng)常無法通過咧栗,應(yīng)該是時(shí)間復(fù)雜度過高的原因吧。

效率
效率

分析
這個(gè)方法的原理很簡單虱肄,首先將兩個(gè)數(shù)組個(gè)數(shù)和是奇數(shù)和偶數(shù)的情況所需的取值個(gè)數(shù)以及中間值計(jì)算方法得出致板。然后將兩個(gè)數(shù)組按照從小到大的順序合并成一個(gè)數(shù)組,最后根據(jù)前面得到的中間數(shù)的計(jì)算方法得出中間值咏窿。
時(shí)間復(fù)雜度 : O(m+n) 斟或。
空間復(fù)雜度 : O(m+n) .

這個(gè)方法可以在做一個(gè)優(yōu)化,如下:


public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int needNum = 0;
        int needIndex = 0;
        double median = 0;
        int big = 0;
        int stand = 0;
        int[] insertnum ;

        int m = nums1.length;
        int n = nums2.length;

        if((m+n)%2==0){
            needNum = 2;
            needIndex = (m+n)/2-1;
        }else{
            needNum=1;
            needIndex = (m+n)/2;
        }
        insertnum = new int[(m+n)/2+1];
        int j =0;
        int k = 0;
        for(int i =0;i<m+n;i++){

            if(k==n){
                insertnum[i] = nums1[j];
                j++;
            }
            else if(j==m){
                insertnum[i] = nums2[k];
                k++;
            }else if(nums1[j]>nums2[k]){
                insertnum[i] = nums2[k];
                k++;
            }else{
                insertnum[i] = nums1[j];
                j++;
            }

             if (i >= needIndex ){
                if (needNum == 1){
                    median = Double.valueOf(insertnum[needIndex]);
                    return median;
                }else{
                     if (i == needIndex+1){
                         median = Double.valueOf(insertnum[needIndex]+insertnum[needIndex+1]) /2.0;
                         return median;
                     }
                }
            }

        }
    return median;
    }
}

分析
這個(gè)方法優(yōu)化的地方就是通過事先得到中間值的位置集嵌,在得到該位置的數(shù)值之后就不繼續(xù)往下走了萝挤。

效率
效率

方法二:

public class Solution {
       public double findMedianSortedArrays(int[] nums1, int[] nums2) {
           int N1 = nums1.length;
           int N2 = nums2.length;
           if (N1 < N2)
               return findMedianSortedArrays(nums2, nums1);    // Make sure A2 is the shorter one.

           if (N2 == 0)
               return ((double) nums1[(N1 - 1) / 2] + (double) nums1[N1 / 2]) / 2;  // If A2 is empty

           int lo = 0, hi = N2 * 2;
           while (lo <= hi) {
               int mid2 = (lo + hi) / 2;   // Try Cut 2
               int mid1 = N1 + N2 - mid2;  // Calculate Cut 1 accordingly

               double L1 = (mid1 == 0) ? Integer.MIN_VALUE : nums1[(mid1 - 1) / 2];    // Get L1, R1, L2, R2 respectively
               double L2 = (mid2 == 0) ? Integer.MIN_VALUE : nums2[(mid2 - 1) / 2];
               double R1 = (mid1 == N1 * 2) ? Integer.MAX_VALUE : nums1[(mid1) / 2];
               double R2 = (mid2 == N2 * 2) ? Integer.MAX_VALUE : nums2[(mid2) / 2];

               if (L1 > R2)
                   lo = mid2 + 1;        // A1's lower half is too big; need to move C1 left (C2 right)
               else if (L2 > R1)
                   hi = mid2 - 1;    // A2's lower half too big; need to move C2 left.
               else
                   return ((L1 > L2 ? L1 : L2) + (R1 > R2 ? R2 : R1)) / 2;    // Otherwise, that's the right cut.
           }
           return -1;
       }
   }
效率
效率

分析

這個(gè)問題比較困難,很多方法都是把他分成兩種情況根欧,奇數(shù)個(gè)和偶數(shù)個(gè)怜珍。實(shí)際上這樣有點(diǎn)復(fù)雜,我們可以把這兩個(gè)情況合并成一種情況咽块。

首先绘面,我們來重新定義一下'median'的含義:

“如果我們把一個(gè)有序的array分成兩個(gè)等長的部分,那么median就是比較小的那一部分的最大值和比較大的那一部分的最小值的平均值侈沪。即劃分線兩邊的數(shù)揭璃。”

比如[1,2],我們的分割線就應(yīng)該在1亭罪,2之間瘦馍,[1 | 2],結(jié)果是(1 + 2)/2.0 = 1.5。
同理应役,如果是兩個(gè)有序的數(shù)組情组,那么我們只需要保證兩個(gè)數(shù)組劃分線右側(cè)的數(shù)都大于左側(cè)的數(shù),并且兩個(gè)數(shù)組左側(cè)數(shù)字個(gè)數(shù)的和等于右側(cè)數(shù)字個(gè)數(shù)的和箩祥。

我們使用‘|’來代表分割線院崇,(n|n)來表示分割線正好在某個(gè)數(shù)字n上。
比如[1 2 3],得到的分割后的數(shù)組像這樣[1 (2|2) 3],z中間值就是(2+2)/2.0 = 2

我們使用L代表分割線左邊的數(shù)袍祖,R代表分割線右邊的數(shù)底瓣。

可以觀察到左邊的數(shù)和右邊的數(shù)有這樣的規(guī)律。

N Index of L / R
1 0 / 0
2 0 / 1
3 1 / 1
4 1 / 2
5 2 / 2
6 2 / 3
7 3 / 3
8 3 / 4

可以得到 L = (N-1)/2, R = N/2. 中間值為(L + R)/2 = (A[(N-1)/2] + A[N/2])/2

為了更方便的計(jì)算兩個(gè)數(shù)組的情況蕉陋,我們添加一些空位在數(shù)字之間捐凭。用'#'代表拨扶,用'position'來表示增加空格后的位置信息。

[1 2] -> [# 1 # 2 #] (N=2)
position 0 1 2 3 4 (N_Position = 5)

[3 4] -> [# 3 # 4 #] (N=2)
position 0 1 2 3 4 (N_Position = 5)

可以看出N_Position = 2N+1.所以分割線是在N茁肠,index(L) = (CutPosition-1)/2, index(R) = (CutPosition)/2.

當(dāng)我們分割這兩個(gè)數(shù)組的時(shí)候要注意患民,總共有2N1+2N2+2的位置,所以垦梆,分割線的每一邊都要有N1+N2個(gè)位置匹颤,剩下的2個(gè)就是切線的位置,所以當(dāng)我們A2分割線在 C2 = K , 那么A1的分割線就必須要在C1 = N1 + N2 - k. 比如, C2 = 2, 那么 C1 = 2 + 2 - C2 = 2.

[# 1 | 2 #]
[# 3 | 4 #]
現(xiàn)在我們有了兩個(gè)L和兩個(gè)R奶赔。
L1 = A1[(C1-1)/2]; R1 = A1[C1/2];
L2 = A2[(C2-1)/2]; R2 = A2[C2/2];

L1 = A1[(2-1)/2] = A1[0] = 1; R1 = A1[2/2] = A1[1] = 2;
L2 = A2[(2-1)/2] = A2[0] = 3; R2 = A1[2/2] = A1[1] = 4;

那么我們要怎么判斷這個(gè)分割線是不是符合規(guī)則的呢惋嚎?我們只要左邊的數(shù)字都小于右邊的數(shù)字。首先這兩個(gè)數(shù)組都是有序的站刑,所以L1,L2是左半邊數(shù)組里最大的數(shù),R1,R2是右半邊數(shù)組里最小的數(shù)鼻百,L1<=R1.L2<=R2.
所以我們只要判斷L1<=R2,L2<=R1就可以了绞旅。

現(xiàn)在我們用簡單的二分法查找就可以得到答案了。

如果L1>R2,代表A1左邊有過多較大的數(shù)字温艇,我們必須把C1往左移因悲,同時(shí)需要把C2往右移。
如果L2>R1,代表A2左邊有過多較大的數(shù)字勺爱,我們必須把C2往左移晃琳,同時(shí)需要把C1往右移。
反之則相反琐鲁。

當(dāng)我們找到正確的分割線位置的時(shí)候卫旱,中間值 = (max(L1, L2) + min(R1, R2)) / 2。

1.因?yàn)镃1和C2可以根據(jù)彼此的值互相推斷围段,我們可以使用較短的數(shù)組(設(shè)為A2)并且只移動(dòng)C2顾翼,然后計(jì)算出C1。這樣我們的時(shí)間復(fù)雜度就是O(log(min(N1, N2)))

2.只有在極限的條件下分割線會(huì)在0,或者2N的index奈泪。比如适贸,C2=2N2,R2 = A2[2*N2/2] = A2[N2].這種情況下他有一邊是沒有數(shù)值的,我們可以把它當(dāng)成是極大或者極小涝桅,L = INT_MIN 拜姿,R = INT_MAX.

時(shí)間復(fù)雜度 : O(log(min(m,n))) 。m,n是兩個(gè)數(shù)組的長度冯遂。
空間復(fù)雜度 : O(1) .

方法三:

public class Solution {
  public double findMedianSortedArrays(int A[], int B[]) {
       int n = A.length;
       int m = B.length;
       // the following call is to make sure len(A) <= len(B).
       // yes, it calls itself, but at most once, shouldn't be
       // consider a recursive solution
       if (n > m)
           return findMedianSortedArrays(B, A);

       // now, do binary search
       int k = (n + m - 1) / 2;
       int l = 0, r = Math.min(k, n); // r is n, NOT n-1, this is important!!
       while (l < r) {
           int midA = (l + r) / 2;
           int midB = k - midA;
           if (A[midA] < B[midB])
               l = midA + 1;
           else
               r = midA;
       }

       // after binary search, we almost get the median because it must be between
       // these 4 numbers: A[l-1], A[l], B[k-l], and B[k-l+1]

       // if (n+m) is odd, the median is the larger one between A[l-1] and B[k-l].
       // and there are some corner cases we need to take care of.
       int a = Math.max(l > 0 ? A[l - 1] : Integer.MIN_VALUE, k - l >= 0 ? B[k - l] : Integer.MIN_VALUE);
       if (((n + m) & 1) == 1)
           return (double) a;

       // if (n+m) is even, the median can be calculated by
       //      median = (max(A[l-1], B[k-l]) + min(A[l], B[k-l+1]) / 2.0
       // also, there are some corner cases to take care of.
       int b = Math.min(l < n ? A[l] : Integer.MAX_VALUE, k - l + 1 < m ? B[k - l + 1] : Integer.MAX_VALUE);
       return (a + b) / 2.0;
   }
}
效率
效率

分析
這個(gè)方法的原理和上一個(gè)方法是大致相同的蕊肥。
時(shí)間復(fù)雜度 : O(log(min(m,n))) 。m,n是兩個(gè)數(shù)組的長度债蜜。
空間復(fù)雜度 : O(1) .

方法四:

public class Solution {
  public double findMedianSortedArrays(int[] A, int[] B) {
              int m = A.length, n = B.length;
              int l = (m + n + 1) / 2;//position of l element (not the index)
              int r = (m + n + 2) / 2;//position of r element (not the index)
              return (getkth(A, 0, B, 0, l) + getkth(A, 0, B, 0, r)) / 2.0;
          }

  public double getkth(int[] A, int aStart, int[] B, int bStart, int k) {
          //when the start position is bigger than A.length -1 ,means the median doesn't in the A.
          if (aStart > A.length - 1) return B[bStart + k - 1];
          if (bStart > B.length - 1) return A[aStart + k - 1];
          if (k == 1) return Math.min(A[aStart], B[bStart]);

          int aMid = Integer.MAX_VALUE, bMid = Integer.MAX_VALUE;
          if (aStart + k/2 - 1 < A.length) aMid = A[aStart + k/2 - 1];
          if (bStart + k/2 - 1 < B.length) bMid = B[bStart + k/2 - 1];

          if (aMid < bMid)
              return getkth(A, aStart + k/2, B, bStart, k - k/2);// Check: aRight + bLeft
          else
              return getkth(A, aStart, B, bStart + k/2, k - k/2);// Check: bRight + aLeft
  }

}
效率
效率

分析
這個(gè)方法的原理是這樣的晴埂,從兩個(gè)數(shù)組中間值開始通過遞歸對(duì)比究反,每次排除一半的選項(xiàng)。如果A的中間值小于B的中間值則保留a的右側(cè)數(shù)字和b的左側(cè)數(shù)字儒洛。反之相反精耐。然后得到l和r位置的數(shù)字,再相加除以2即可。需要注意的是這里的l和r不是index琅锻,不是從零開始的卦停。如果兩個(gè)數(shù)組的長度的和是奇數(shù)的話l和r是相同的,偶數(shù)則不同恼蓬。
時(shí)間復(fù)雜度 : O(log(m + n))
空間復(fù)雜度 : O(1)

如果你有更好的辦法或者對(duì)我這里的描述有其他看法惊完,請(qǐng)聯(lián)系我。謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末处硬,一起剝皮案震驚了整個(gè)濱河市小槐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荷辕,老刑警劉巖凿跳,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疮方,居然都是意外死亡控嗜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門骡显,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疆栏,“玉大人,你說我怎么就攤上這事惫谤”诙ィ” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵石挂,是天一觀的道長博助。 經(jīng)常有香客問我,道長痹愚,這世上最難降的妖魔是什么富岳? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮拯腮,結(jié)果婚禮上窖式,老公的妹妹穿的比我還像新娘。我一直安慰自己动壤,他們只是感情好萝喘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般阁簸。 火紅的嫁衣襯著肌膚如雪爬早。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天启妹,我揣著相機(jī)與錄音筛严,去河邊找鬼。 笑死饶米,一個(gè)胖子當(dāng)著我的面吹牛桨啃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播檬输,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼照瘾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丧慈?” 一聲冷哼從身側(cè)響起析命,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伊滋,沒想到半個(gè)月后碳却,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笑旺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了馍资。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筒主。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸟蟹,靈堂內(nèi)的尸體忽然破棺而出乌妙,到底是詐尸還是另有隱情,我是刑警寧澤建钥,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布藤韵,位于F島的核電站,受9級(jí)特大地震影響熊经,放射性物質(zhì)發(fā)生泄漏泽艘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一镐依、第九天 我趴在偏房一處隱蔽的房頂上張望匹涮。 院中可真熱鬧,春花似錦槐壳、人聲如沸然低。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雳攘。三九已至带兜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吨灭,已是汗流浹背刚照。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沃于,地道東北人涩咖。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像繁莹,于是被迫代替她去往敵國和親檩互。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容