【算法分析與設計】【第二週】4.Median of Two Sorted Arrays

題目來源: https://leetcode.com/problems/median-of-two-sorted-arrays/description/

題目大意:找出兩個有序數組(升序)nums1和nums2的中位數。其中nums1的大小爲m,nums2的大小爲n。

最近兩週都在學習分治的思想,所以選了這麼一個題作爲應用試試手。

思路大致有這麼幾種:
1.將兩個數組直接合併成一個數組,使用快速排序後,直接求解中位數。這種做法很直接暴力,但沒什麼意思,主要是考察對排序算法的熟悉程度,快排也是利用分治算法的一種排序算法,寫一遍算作對快排算法的一次複習。
時間複雜度O(nlogn)。

//快速排序
void quicksort(int* arr, int left, int right) {
    if (left < right) {
        int i = left, j = right, x = arr[left];
        while (i < j) {
            while(i < j && arr[j] >= x) // 從右向左找第一個小於x的數
                j--; 
            if(i < j)
                arr[i++] = arr[j];        
            while(i < j && arr[i] < x) // 從左向右找第一個大於等於x的數
                i++; 
            if(i < j)
                arr[j--] = arr[i];
        }
        arr[i] = x;
        quicksort(arr, left, i - 1); // 遞歸調用
        quicksort(arr, i + 1, right);
    }
}

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int arr[nums1Size+nums2Size];
    for (int i = 0; i < nums1Size; i++) {
        arr[i] = *(nums1+i);
    }
    int j = 0;
    for (int i = nums1Size; i < nums1Size+nums2Size; i++) {
        arr[i] = *(nums2+(j++));
    }
    quicksort(arr, 0, nums1Size+nums2Size-1);
    int mid = (nums1Size+nums2Size)*0.5;
    if ((nums1Size+nums2Size)%2 == 1) return arr[mid];
    else return (arr[mid]+arr[mid-1])*0.5;
}

2.巧妙利用兩個數組有序**這一條件,先依次比較兩個數組,小的傳入新的數組。其中短的數組用完了,即全部放入到新數組中去了,那麼長數組中剩下的那一段就可以直接拿來放入到新數組中去了。
時間複雜度O(min{n, m})。

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {    
    //兩個有序數組的合併函數
    int arr[nums1Size+nums2Size];
    int i = 0, j = 0, k = 0;
    while (i < nums1Size && j < nums2Size) {
        if (*(nums1+i) <= *(nums2+j)) {
            arr[k] = *(nums1+i);
            i++;
        } else {
            arr[k] = *(nums2+j);
            j++;
        }
        k++;
    }
    // 將較長一串的後半截直接放入 
    while(i < nums1Size) 
        arr[k++] = nums1[i++];
    while(j < nums2Size)
        arr[k++] = nums2[j++];          
    int mid = (nums1Size+nums2Size)*0.5;
    if ((nums1Size+nums2Size)%2 == 1) return arr[mid];
    else return (arr[mid]+arr[mid-1])*0.5;
}

3.利用剛剛學過的分治算法(《算法概論》P54),也是先將兩個數組直接合併成一個數組,隨機挑選v。
時間複雜度O(m+n)。


4.巧妙利用中位數的定義。根據中位數的特點,中位數最終將原數組分爲兩個子數組subArr1和subArr2,且**subArr1的所有元素均小於subArr2
具體來說,對於一個大小爲n的數組arr而言,如果n是偶數,則中位數將該數組分爲等長且長度爲n/2的兩個子數組。如果n是奇數,在這裏,我們用中位數將該數組分爲兩個子數組,小的子數組比大的子數組長度小1。
在本題中,我們要找的中位數,將nums1和nums2數組合並後分爲兩個子數組,那麼兩個子數組subArr1和subArr2的長度關係滿足

len(subArr1)=len(subArr2)=(m+n)0.5

(len(subArr1))+1=len(subArr2)=(m+n)0.5+1

對於子數組subArr1,我們可以理解成由nums1的某一部分和nums2的某一部分合並組成。因此,我們把nums1分成兩部分,分別爲num1_small和num1_large。nums2同理,分成num2_small和num2_large。由於subArr1中的所有元素均小於subArr2,所以subArr1應該由num1_small和num2_small組成。(當然,也可能出現num1_small或num2_small爲空的現象。)
子數組subArr2同理,由num1_large和num2_large組成。
subArr1 = num1_small + num2_small
subArr2 = num1_large + num2_large

用i、j分別把nums1和nums2按上述方式分開。

num1_small = num1[0]+...+num1[i-1]
num1_large = num1[i]+...+num1[m-1]

num2_small = num2[0]+...+num2[j-1]
num2_large = num2[j]+...+num2[n-1]

subArr1 subArr2
num1[0]+…+num1[i-1] num1[i]+…+num1[m-1]
num2[0]+…+num2[j-1] num2[j]+…+num2[n-1]

根據上面推出的subArr1和subArr2的長度關係,我們很容易得出

i+j=mi+nj=(m+n)0.5

i+j+1=mi+nj=(m+n)0.5+1

從而得出關係
j=(m+n+1)0.5i

以簡化算法。

比較一下4種方法的時間複雜度:

方法 時間複雜度 難度
快排 O(nlogn) 應掌握
利用兩個數組有序 O(min{n, m}) 較容易
Selection求第k大的數 O(m+n) 目前還沒寫出來
巧妙利用中位數的定義 O(logmin{n, m})

以上是我對題目4的一些解析。寫完這篇博客後,和室友交流,室友說利用分治思想中的歸併排序做此題,亦可達到O(log(n, m)) 。分治思想博大精深,我還是需要繼續進行探索。

參考:http://www.cnblogs.com/A_ming/archive/2010/04/15/1712313.html
https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/

發佈了29 篇原創文章 · 獲贊 10 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章