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

2021-10-09 10:19:39 字數 3552 閱讀 6349

例題 :查詢兩個有序陣列合併後的中位數

【題目】 兩個有序陣列查詢合併之後的中位數。給定兩個大小為 m 和 n 的正序(從小到大)陣列 nums1 和 nums2。請你找出這兩個正序數組合在一起之後的中位數,並且要求演算法的時間複雜度為 o(log(m + n))。

你可以假設 nums1 和 nums2 不會同時為空,所有的數字全都不相等。還可以再假設,如果數字個數為偶數個,中位數就是中間偏左的那個元素。

例如:nums1 = [1, 3, 5, 7, 9]

nums2 = [2, 4, 8, 12]

輸出 5。

我們先看一下複雜度的分析。這裡的 nums1 和 nums2 都是有序的,這讓我們第一時間就想到了歸併排序。方法很簡單,我們把兩個陣列合併,就得到了合在一起後的有序陣列。這個動作的時間複雜度是 o(m+n)。接著,我們從陣列中就可以直接取出中位數了。很可惜,這並不滿足題目的時間複雜度 o(log(m + n)) 的要求。

接著,我們來看一下這個問題的定位。題目中有乙個關鍵字,那就是「找出」。很顯然,我們要找的目標就藏在 nums1 或 nums2 中。這明顯就是乙個查詢問題。而在查詢問題中,我們學過的知識是分治法下的二分查詢。

回想一下,二分查詢適用的重要條件就是,原陣列有序。恰好,在這個問題中 nums1 和 nums2 分別都是有序的。而且二分查詢的時間複雜度是 o(logn),這和題目中給出的時間複雜度 o(log(m + n)) 的要求也是不謀而合。因此,經過分析,我們可以大膽猜測,此題極有可能要用到二分查詢。

我們再來看一下資料結構方面。如果要用二分查詢,就需要用到若干個指標,去約束查詢範圍。除此以外,並不需要去定義複雜的資料結構。也就是說,空間複雜度是 o(1) 。

好了,接下來,我們就來看一下二分查詢如何能解決這個問題。二分查詢需要乙個**點,去把原來的大問題,拆分成兩個部分,並在其中一部分繼續執行二分查詢。既然是查詢中位數,我們不妨先試試以中位數作為切分點,看看會產生什麼結果。如下圖所示:

經過切分後,兩個陣列分別被拆分為 3 個部分,合在一起是 6 個部分。二分查詢的思路是,需要從這 6 個部分中,剔除掉一些,讓查詢的範圍縮小。那麼,我們來思考乙個問題,在這 6 個部分中,目標中位數一定不會發生在哪幾個部分呢?

中位數有乙個重要的特質,那就是比中位數小的數字個數,和比中位數大的數字個數,是相等的。圍繞這個性質來看,中位數就一定不會發生在 c 和 d 的區間。

如果中位數在 c 部分,那麼在 nums1 中,比中位數小的數字就會更多一些。因為 4 < 5(nums2 的中位數小於 nums1 的中位數),所以在 nums2 中,比中位數小的數字也會更多一些(最不濟也就是一樣多)。因此,整體來看,中位數不可能在 c 部分。同理,中位數也不會發生在 d 部分。

接下來,我們就可以在查詢範圍內,剔除掉 c 部分(永遠比中位數大的數字)和 d 部分(永遠比中位數小的數字),這樣我們就成功地完成了一次二分動作,縮小了查詢範圍。然而這樣並沒結束。剔除掉了 c 和 d 之後,中位數有可能發生改變。這是因為,c 部分的數字個數和 d 部分數字的個數是不相等的。剔除不相等數量的「小數」和「大數」後,會造成中位數的改變。

為了解決這個問題,我們需要對剔除的策略進行修改。乙個可行的方法是,如果 c 部分數字更少為 p 個,則剔除 c 部分;並只剔除 d 部分中的 p 個數字。這樣就能保證,經過一次二分後,剔除之後的陣列的中位數不變。

應該剔除 c 部分和 d 部分。但 d 部分更少,因此剔除 d 和 c 中的 9。

二分查詢還需要考慮終止條件。對於這個題目,終止條件必然是某個陣列小到無法繼續二分的時候。這是因為,每次二分剔除掉的是更少的那個部分。因此,在終止條件中,查詢範圍應該是乙個大陣列和乙個只有 1~2 個元素的小陣列。這樣就需要根據大陣列的奇偶性和小陣列的數量,拆開 4 個可能性:

可能性一:nums1 奇數個,nums2 只有 1 個元素。例如,nums1 = [a, b, c, d, e],nums2 = [m]。此時,有以下 3 種可能性:

如果 m < b,則結果為 b;

如果 b < m < c,則結果為 m;

如果 m > c,則結果為 c。

這 3 個情況,可以利用 "a?b:c" 合併為乙個表示式,即 m < b ? b : (m < c ? m : c)。

可能性二:nums1 偶數個,nums2 只有 1 個元素。例如,nums1 = [a, b, c, d, e, f],nums2 = [m]。此時,有以下 3 種可能性:

如果 m < c,則結果為 c;

如果 c < m < d,則結果為 m;

如果m > d,則結果為 d。

這 3 個情況,可以利用"a?b:c"合併為乙個表示式,即 m < c ? c : (m < d? m : d)。

可能性三:nums1 奇數個,nums2 有 2 個元素。例如,nums1 = [a, b, c, d, e],nums2 = [m,n]。此時,有以下 6 種可能性:

如果 n < b,則結果為 b;

如果 b < n < c,則結果為 n;

如果 c < n < d,則結果為 max(c,m);

如果 n > d,m < c,則結果為 c;

如果 n > d,c < m < d,則結果為 m;

如果 n > d,m > d,則結果為 d。

其中,4~6 可以合併為,如果 n > d,則返回 m < c ? c : (m < d ? m : d)。

可能性四:nums1 偶數個,nums2 有 2 個元素。例如,nums1 = [a, b, c, d, e, f],nums2 = [m,n]。此時,有以下 6 種可能性:

如果 n < b,則結果為 b;

如果 b < n < c,則結果為 n;

如果 c < n < d,則結果為 max(c,m);

如果 n > d,m < c,則結果為 c;

如果 n > d,c < m < d,則結果為 m;

如果 n > d,m > d,則結果為 d。與可能性 3 完全一致。

不難發現,終止條件都是 if 和 else 的判斷,雖然邏輯有點複雜,但時間複雜度是 o(1) 。為了簡便,我們可以假定,nums1 的數字數量永遠是不少於 nums2 的數字數量。

因此,我們可以編寫如下的**:

#includeint find(int* a,int begina,int enda,int* b,int beginb,int endb)else	}	

//劃分

int mida = (begina + enda) / 2;

int midb = (beginb + endb) / 2;

//劃分原則 中位數比一組中間數大的小(在左邊), 比一組中間數小的大(在右邊),兩組增刪一致,按b的即元素少的那一組

if( a[mida] < b[midb]) else

}int main();

int b = ;

int lengtha = sizeof(a) / sizeof(a[0]);

int lengthb = sizeof(b) / sizeof(b[0]);

printf("兩組有序數列的合併數列中位數為%d\n",find(a,0,lengtha - 1,b,0,lengthb - 1));

return 0;

}

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

題目 給定兩個大小為 m 和 n 的有序陣列nums1和nums2。請你找出這兩個有序陣列的中位數,並且要求演算法的時間複雜度為 o log m n 你可以假設nums1和nums2不會同時為空。示例 1 nums1 1,3 nums2 2 則中位數是 2.0 class solution for ...

兩個有序陣列中位數

大小m和n分別有兩個排序陣列a和b。找到兩個排序陣列的中值。總的執行時間複雜度應該是o log m n class solution return findkth a,b,0,0,m,n,s 2 findkth a,b,0,0,m,n,s 2 1 2 private double findkth v...

兩個有序陣列中位數

首先我們可以不斷分割得到這個kkk。首先兩個陣列都分配k 2 frac 2k 如果第k 2 frac 2k 個數,第乙個陣列小於第二個陣列,那麼第乙個陣列的k 2 frac 2k 個數一定不是答案。問題可以變成乙個子問題,上乙個陣列從k2 1 frac 1 2k 1開始。相當於我們每次要去掉k 2 ...