1. 程式人生 > >演算法設計與分析》第六週作業

演算法設計與分析》第六週作業

《演算法設計與分析》第六週作業

標籤(空格分隔): 課堂作業

文章目錄

姓名:李**
學號: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);
            }
        }
    }
};