演算法設計與分析》第六週作業
《演算法設計與分析》第六週作業
標籤(空格分隔): 課堂作業
文章目錄
姓名:李**
學號:16340114
題目:Median of Two Sorted Arrays(https://leetcode.com/problems/median-of-two-sorted-arrays/description/)
題目概要
給定兩個有序的數列,找出兩個數列歸併之後的中位數。其實這道題就是“在兩個有序數列中找第k小的數”的特殊情況。
思路
先來解決“在兩個有序數列中找第k小的數”。一種很直觀的思路就是把兩個數列利用歸併排序合起來,直接找中位數。這個方法很簡單,但是演算法複雜度是O(m + n)【數列1長度為m,數列2長度為n】,並不算很快。利用二分查詢的思想可以更快地找到第k小的數。
我們先把兩個陣列各分成兩組,並比較兩個陣列中間元素的大小關係,企圖找到第k個數落在四段中的哪一段。可惜現實並沒有這麼理想,好在我們能排除第k個數不在四段中的哪一段。
情況1:(m + n) / 2 < k 【說明第k個數在合併後的數列靠後半部分的位置】
(i)arr1[mid1] < arr2[mid2]
如圖所示:數列1的中間元素小於數列2的中間元素時,數列1的前半部分是絕對在合併數列的前半部分的。下面是比較極端的情況:
(1)數列1的前半部分比數列2的前半部分都小,那這一部分是絕對排在合併數列的最前面的,這樣的話可以將數列1的前半部分砍去。
(2)數列1的前半部分僅小於數列2的中間元素,即使是這樣數列1的前半部分也只能排在合併數列的前半部分,這部分也可以放心地砍去。
(3)介於(1)與(2)兩個極端之間的中間情況,這些情況中數列1的前半部分也是可以排除掉的。
(ii)arr1[mid1] >= arr2[mid2]
交換arr1與arr2,重複(i)
情況2:(m + n) / 2 >= k 【說明第k個數在合併後的數列靠前半部分的位置】
和情況1的思路相似,這次是要把兩個數列中的一個的後半部分砍去即可。
(i)arr1[mid1] < arr2[mid2]
砍去arr2的後半部分
(ii)arr1[mid1] >= arr2[mid2]
交換arr1與arr2,重複(i)
將對砍掉一半的數列與另一數列重複該操作,直到某一個數列完全被砍沒了之後,第k個數自然就浮現了。
得到尋找第k個數的方法後,找中位數就很簡單了。
如果m+n是奇數,只需要找第(m+n)/2個數即可。
如果m+n是偶數,計算第(m+n)/2 - 1個數與第(m+n)/2個數的平均值即可。
具體實現
按照上述思路編寫找第k個數的遞迴函式,basic case為其中一個數列為空,輸出另一數列的第k個元素,遞迴過程按照上述四種情況進行,每次遞迴都通過傳遞vector的begin()和end()迭代器實現切分陣列的效果,迭代的同時根據情況修改k的值(砍掉前半部分就將k減相應的長度,砍後半部分k值不變)。最後呼叫該找第k個元素的函式,找出中位數,完成!演算法複雜度為O(log(m+n))
心得
感覺這個二分查詢比第一週剛看這題時看的那個解法要好懂太多了,果然條條大路通羅馬呀
原始碼:
#define mid1 ( (end1 - nums1) / 2)
#define mid2 ( (end2 - nums2) / 2)
class Solution
{
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
if ( (nums1.size() + nums2.size()) % 2 == 1 )
return kthSmallestNum( (nums1.size() + nums2.size()) / 2,
nums1.begin(), nums2.begin(),
nums1.end(), nums2.end() );
else
return (double)kthSmallestNum( (nums1.size() + nums2.size()) / 2 - 1,
nums1.begin(), nums2.begin(),
nums1.end(), nums2.end() ) / 2
+
(double)kthSmallestNum( (nums1.size() + nums2.size()) / 2,
nums1.begin(), nums2.begin(),
nums1.end(), nums2.end() ) / 2;
}
int kthSmallestNum(int k, vector<int>::iterator nums1, vector<int>::iterator nums2,
vector<int>::iterator end1, vector<int>::iterator end2)
{
if (nums1 == end1)
return *(nums2 + k);
if (nums2 == end2)
return *(nums1 + k);
if (mid1 + mid2 < k)
{
//the kth smallest number is in the second half
if ( *(nums1 + mid1) < *(nums2 + mid2) )
{
//cut the first half of nums1
return kthSmallestNum(k - mid1 - 1, nums1 + mid1 + 1, nums2, end1, end2);
}
else
{
//cut the first half of nums2
return kthSmallestNum(k - mid2 - 1, nums1, nums2 + mid2 + 1, end1, end2);
}
}
else
{
//the kth smallest number is in the first half
if ( *(nums1 + mid1) < *(nums2 + mid2) )
{
//cut the second half of nums1
return kthSmallestNum(k, nums1, nums2, end1, nums2 + mid2);
}
else
{
//cut the second half of nums2
return kthSmallestNum(k, nums1, nums2, nums1 + mid1, end2);
}
}
}
};