題目
給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2。
請你找出這兩個有序數組的中位數,並且要求算法的時間複雜度爲 O(log(m + n))。
你可以假設 nums1 和 nums2 不會同時爲空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
則中位數是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
則中位數是 (2 + 3)/2 = 2.5
來源:力扣(LeetCode)
思路
首先,假設兩個有序數組的長度分別爲m和n,由於兩個數組長度之和 m+n 的奇偶性不確定,因此需要分情況來討論:對於奇數的情況,直接找到最中間的數即可,偶數的話需要求最中間兩個數的平均值。爲了簡化代碼,不分情況討論,使用一個技巧,分別找第 (m+n+1) / 2
個,和 (m+n+2) / 2
個,然後求其平均值,這對奇偶數都是適用的。若 m+n
爲奇數的話,那麼 (m+n+1) / 2
和 (m+n+2) / 2
的值相等,相當於兩個相同的數字相加再除以2,還是其本身。
其次,定義一個函數在兩個有序數組中找到第K個元素,如何實現找到第K個元素?首先,爲了避免拷貝產生新的數組從而增加時間複雜度,使用兩個變量i
和j
分別來標記數組 nums1
和 nums2
的起始位置。然後來處理一些小問題,比如當某一個數組的起始位置大於等於其數組長度時,說明其所有數字均已經被淘汰,相當於一個空數組,實際上就變成了在另一個數組中找數字,當然直接就可以找出來。還有就是,如果 K=1
的話,只要比較 nums1
和 nums2
的起始位置i
和j
上的數字就可以了。難點就在於一般的情況怎麼處理?因爲需要在兩個有序數組中找到第K個元素,爲了加快搜索的速度,可以使用二分法,本題就是要對K
二分,意思是需要分別在 nums1
和 nums2
中查找第 K/2
個元素,注意,這裏由於兩個數組的長度不定,所以有可能某個數組沒有第 K/2
個數字,所以需要先 檢查 一下,數組中到底存不存在第 K/2
個數字,如果存在就取出來,否則就賦值一個整型最大值(目的是要在 nums1
或者 nums2
中先淘汰 K/2
個較小的數字,判斷的依據就是看 midVal1
和 midVal2
誰更小,但如果某個數組的個數不到 K/2
個,自然無法淘汰,所以將其對應的 midVal
值設爲整型最大值,以保證其不會被淘汰),若某個數組沒有第 K/2
個數字,則淘汰另一個數組的前 K/2
個數字即可。
舉例說明,比如 nums1
= {3},nums2
= {2, 4, 5, 6, 7},K=4,要找兩個數組混合中第4個數字,則分別在 nums1
和 nums2
中找第2個數字,而 nums1
中只有一個數字,不存在第二個數字,則 nums2
中的前2個數字可以直接跳過,爲啥呢,因爲要求的是整個混合數組的第4個數字,不管 nums1
中的那個數字是大是小,第4個數字絕不會出現在 nums2
的前兩個數字中,所以可以直接跳過。
最後,有沒有可能兩個數組都不存在第 K/2
個數字呢,這道題裏是不可能的,因爲K
不是任意給的,而是給的 m+n
的中間值,所以必定至少會有一個數組是存在第 K/2
個數字的。最後就是二分法的核心,比較這兩個數組的第 K/2
小的數字 midVal1
和 midVal2
的大小,如果第一個數組的第 K/2
個數字小的話,那麼說明要找的數字肯定不在 nums1
中的前 K/2
個數字,可以將其淘汰,將 nums1
的起始位置向後移動 K/2
個,並且此時的K也自減去 K/2
,調用遞歸,舉個例子來說,比如 nums1
= {1, 3},nums2
= {2, 4, 5},K=4
,要找兩個數組混合中第4個數字,那麼分別在 nums1
和 nums2
中找第2個數字,nums1
中的第2個數字是3,nums2
中的第2個數字是4,由於3小於4,所以混合數組中第4個數字肯定在 nums2
中,可以將 nums1
的起始位置向後移動 K/2 個。反之,淘汰 nums2
中的前 K/2
個數字,並將 nums2
的起始位置向後移動 K/2
個,並且此時的K也自減去 K/2
,調用遞歸即可。
C++代碼
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
int m = nums1.size(), n = nums2.size(), left = (m + n + 1) / 2, right = (m + n + 2) / 2;
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k)
{
if (i >= nums1.size())
return nums2[j + k - 1];
if (j >= nums2.size())
return nums1[i + k - 1];
if (k == 1)
return min(nums1[i], nums2[j]);
int midVal1 = (i + k / 2 - 1 < nums1.size()) ? nums1[i + k / 2 - 1] : INT_MAX;
int midVal2 = (j + k / 2 - 1 < nums2.size()) ? nums2[j + k / 2 - 1] : INT_MAX;
if (midVal1 < midVal2)
{
return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
}
else
{
return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
}
}
};