1. 程式人生 > >查詢兩個有序陣列的中位數

查詢兩個有序陣列的中位數

問題描述

問題很簡單,就是在兩個有序的整數數組裡(陣列A長度為m, 陣列B長度為n),找到兩個數組合並後的中位數。

要求時間複雜度 O(log(m+n))

也就是說不能用先歸併兩個陣列再直接查詢的辦法。

中位數

中位數就是在一個有序陣列中,位於中間的數字,假如陣列元素個數為偶數,則取兩個中間數字的平均數。

例:
1,2,3,4,5 中位數為:3
1,2,3,4 的中位數為:2.5

演算法講解

這個問題其實看起來挺簡單的,網上的一些部落格裡說的,很多都是一個大致思想,細節有誤,程式碼也有誤。真是誤人子弟。

陣列等長

假如陣列等長的話,那麼很簡單就能解決,因為每次比較陣列的中間數字,把數字大的陣列扔掉右半邊,把數字小的陣列扔掉左半邊,每次迴圈,到最後就能知道中位數是多少了。

But!注意事項

你以為這麼簡單就OK了? 當然不是!在陣列等長的時候需要注意以下幾個問題。

  1. 陣列等長,說明陣列AB的元素和為偶數,最後求的的中位數一定是兩個中間數字的平均。
  2. 陣列在遞迴的分割過程中,出現兩個陣列都是偶數個數的時候,比較的其實不是中間的數字(是中間偏下的數字)。因此在遇到陣列如:3,4,5,6與1,2,7,8的時候,第一次比較是42發現4比較大,因此把5,6扔掉,另外一個數字扔掉1,2,但是我們可以發現,這個兩個陣列的中位數是4,5兩個數字的平均數4.5,但是在剛才比較的過程中已經把5丟掉了。所以這個時候要注意。
  3. 陣列不是在遇見A[median1] == B[median2]
    的時候停止。(因為1,2,3,4和1,2,4,5)在第一次比較的時候,median1 = 1, median2 = 1, 那麼A[1] = 2, B[1] = 2雖然兩個元素相等,但是這2並不是中位數,中位數是2.5。但是假如在比較發現相等的時候,陣列比較的開始和結束是奇數個元素的話,是可以直接認為相等的。(例如: 1,2,3,4,5和1,2,3,5,7)這個兩個陣列的中位數就是3。

解決辦法

  • 針對注意事項1:我們只用記得返回元素的時候一定是return (double)(a+b)/2就可以了。

  • 針對注意事項2:在判斷兩個中位數大小之後,請判斷當前(因為是遞迴,所以每次是有開始結束位置的)是不是兩個偶數個元素的中位數比較,如果是則在 A[median1] > B[median2]的時候,割掉(end1 - median1 - 1)個元素。同意陣列B割掉也是這麼多個元素。

  • 針對注意事項3:A: 1,2,3,4與B: 1,2,5,7在遇到A[median1] == B[median2]情況,雖然沒有直接停止,但是也是已經找到中位數了,同樣判斷當前比較的範圍是不是都是奇數個元素,如果是:返回這個數字!它就是中位數,如果不是,則返回這個數字2與其後面數字A陣列2後面是3和B陣列2的後面5相對較小數字3的平均數(2 + min(3,5))/2

至此已經解決了等長度的陣列求中位數!

陣列不等長

其實陣列不等長與陣列等長的解決辦法基本一樣!
但是還是要注意每次遞迴切割的長度問題。

下面的圖可以簡單解釋一下分割的情況:
陣列分割

首先在我們切割的時候,先判斷陣列A和B的長短情況,哪個短,哪個是A,因為切割的時候如果以長陣列為準,有可能切掉一半後,短陣列早就越界訪問了。

注意:圖裡是A[median1] < B[median2]情況,假如是A[median1] > B[median2]的情況,便如陣列等長情況一樣,需要判斷是不是雙方都是偶數個元素,如果是則切割的袁術個數不是end1 - median1而是end1 - median1 - 1個數字。

然後我們已經知道如何解決整個問題了。

遞迴結束條件

在陣列A,B元素個數和不同情況,結束條件不同。

奇數個數

陣列A和陣列B的元素個數和為奇數個:

  1. A[median1] == B[median2]一定結束。
  2. 陣列A只有最後一個元素,假如A[median1] < B[median2]返回B[median2]。假如A[median1] > B[median2] && A[median1] < B[median2+1]返回A[median1]。最後假如A[median1] > B[median2+1]返回B[median2+1]

以上兩個是結束條件,那麼有一種情況是死迴圈條件。就是當迴圈到一定程度,即陣列A只剩下兩個元素的時候median1 == start11,2且比B[median2]小的時候,已經沒有任何切割的了。就會死迴圈。(其實因為A[median1] > B[median2]的時候會少切一個元素,所以不論比B[median2]大或小都需要手動切割)。
這個時候手動切割掉A中不需要的元素就好了, A[median1]小就切割掉A[median1]反之切割掉A[end1]

然後繼續遞迴就好了,在上面已經說明了遇到A只有一個元素情況的結束條件。

注意!!!! 以上是建立在陣列A和陣列B總元素數量和為奇數情況。

偶數個數

偶數個數在結束條件上很麻煩!!!!
大家自己寫一個例子:

[1,2];[3,4];  
---
[1,4];[2,3];
---
[1,3];[2,4,6,7];
---
[5,6];[1,2,4,7];

以上都需要一個個分析,分析A[start1]的位置和A[end1]的位置

想想什麼樣子的位置情況,應該是哪兩個數字的平均數就可以了。不難,但是亂!

程式碼(C++)

以下程式碼在leetcode OJ上已經AC.

//
//  main.cpp
//  MedianOfTwoSortArray
//
//  Created by Alps on 15/11/2.
//  Copyright (c) 2015年 chen. All rights reserved.
//

#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int length1 = (int)nums1.size();
        int length2 = (int)nums2.size();
        //如果有一個數組為空
        if (length1 == 0) {
            if (length2 %2 == 0) {
                return (double)(nums2[length2/2]+nums2[length2/2 - 1])/2;
            }else{
                return (double)nums2[length2/2];
            }
        }
        //如果有一個數組為空
        if (length2 == 0) {
            if (length1 %2 == 0) {
                return (double)(nums1[length1/2]+nums1[length1/2 - 1])/2;
            }else{
                return (double)nums1[length1/2];
            }
        }

        //如果把陣列長度短的放在前面
        if (length1 <= length2) {
            return findMedian(nums1, 0, length1-1, nums2, 0, length2-1);
        }else{
            return findMedian(nums2, 0, length2-1, nums1, 0, length1-1);
        }


    }
    //返回相對小的數字
    int minNumber(int a, int b){
        return a < b ? a : b;
    }
    //查詢主函式
    double findMedian(vector<int>& nums1, int start1, int end1, vector<int>& nums2, int start2, int end2){
        // 如果兩個陣列都只有一個數字(為空的已經在之前判斷過了)
        if (start1 == end1 && start2 == end2) {
            return ((double)nums1[0]+(double)nums2[0])/2;
        }
        int offset = 0;//每次切割的偏移量
        int median1 = (start1 + end1)/2;//求中間數字下標
        int median2 = (start2 + end2)/2;//求中間數字下標
        //陣列A只有一個數字,而陣列B不止一個數字的情況
        if (start1 == end1 && start2 != end2) {
            if ((end2 - start2) % 2 == 0) { //判斷陣列B如果是偶數個數字
                if (nums1[start1] >= nums2[median2-1] && nums1[start1] <= nums2[median2+1]) {
                    return (double)(nums1[start1]+nums2[median2])/2;
                }else if(nums1[start1] < nums2[median2-1]){
                    return (double)(nums2[median2-1] + nums2[median2])/2;
                }else{
                    return (double)(nums2[median2] + nums2[median2+1])/2;
                }
            }else{//如果奇數個數字
                if (nums1[start1] >= nums2[median2] && nums1[start1] <= nums2[median2+1]) {
                    return (double)nums1[start1];
                }else if(nums1[start1] < nums2[median2]){
                    return (double)nums2[median2];
                }else{
                    return (double)nums2[median2+1];
                }
            }

        }
        // 如果中間下標數字相等
        if (nums1[median1] == nums2[median2]) {
            if ((nums1.size() + nums2.size())%2 != 0 || end1-start1 % 2 == 0) {//總元素是奇數個或者兩個陣列範圍內都是奇數個元素
                return nums1[median1];
            }else{//否則
                return (double)(nums1[median1]+minNumber(nums1[median1+1] , nums2[median2+1]))/2;
            }

        }else if (nums1[median1] < nums2[median2]){
            if (median1 == start1) {//假如A範圍到兩個數字的情況了
                if ((nums1.size() + nums2.size())%2 == 0) {//麻煩的判斷
                    if (nums1[start1+1] >= nums2[median2+1]) {
                        return (double)(nums2[median2]+nums2[median2+1])/2;
                    }else if(median2 != 0 && nums1[start1+1] <= nums2[median2-1]){
                        return (double)(nums2[median2]+nums2[median2-1])/2;
                    }else{
                        return (double)(nums2[median2]+nums1[start1+1])/2;
                    }
                }else{
                    return findMedian(nums1, start1 + 1, end1, nums2, start2, end2-1);
                }
            }
            offset = median1-start1;//正常的offset
            return findMedian(nums1, start1+offset, end1, nums2, start2, end2-offset);
        }else{
            if (median1 == start1) {
                if ((nums1.size() + nums2.size())%2 == 0) {
                    if (nums1[end1] <= nums2[median2+1]) {
                        return (double)(nums1[median1] + nums1[start1+1])/2;
                    }else if ((nums1[end1] > nums2[median2+1] && nums1[start1] <= nums2[median2+1]) || (median2+2 < nums2.size() && nums1[median1] <= nums2[median2+2]) || (median2+2 >= nums2.size() && nums1[start1] >= nums2[median2+1])){
                        return (double)(nums1[median1]+nums2[median2+1])/2;
                    }else {
                        return (double)(nums2[median2+1]+nums2[median2+2])/2;
                    }
                }else{
                    return findMedian(nums1, start1, end1-1, nums2, start2+1, end2);
                }
            }
            offset = end1-median1;
            if ((end1-start1)%2 != 0 && (end2-start2)%2 != 0) {
                offset -= 1; //需要少切割一個的offset
            }
            return findMedian(nums1, start1, end1-offset, nums2, start2+offset, end2);
        }
        return 0;
    }
};

int main(int argc, const char * argv[]) {
    Solution sl;
    int num1[] = {1,5, 6, 7};
    int num2[] = {2,3,4,8,9,10};
    vector<int> nums1(num1, num1+4);
    vector<int> nums2(num2, num2+6);
    printf("%f\n",sl.findMedianSortedArrays(nums1, nums2));
    return 0;
}

以上就是這個問題的解決程式碼了。我的程式碼量是稍微有點大的。所以可能不是很好看。後面我看看有沒有更簡單的程式碼解決辦法。