題目來源: 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的長度關係滿足
或
對於子數組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的長度關係,我們很容易得出
或
從而得出關係
以簡化算法。
比較一下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/