尋找兩個有序數組的中位數(虛擬數組圖文詳解)

思路一(暴力):

  當看到這個題目的時候可能會覺的是不是系統高估了這個題目,這個這麼簡單,只需要將兩個數組合並,排序然後合併就好了。這樣做確實可以求出中位數,但是並不能說是完成題目的要求,因爲題目的要求時間複雜度是O(log(m + n)),這個暴力算法的時間複雜度很明顯是O(m+n),可以有興趣的可以測一下,這個算法不是本博客的重點,這裏不多贅述,代碼如下:

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        double ret = -1;
        
        vector<double> buff;
        
        //合併兩個數組
        for (auto e : nums1)
            buff.push_back(e);
        for (auto a : nums2)
            buff.push_back(a);
        
        //將合併後的結果進行排序
        sort(buff.begin(), buff.end());
        int size3 = buff.size();
        
        //獲取中位數
        if (size3 % 2 == 0)
        {
            ret = ((buff[size3 / 2] + buff[size3 / 2 - 1]) / 2);
        }
        else
            ret = buff[size3 / 2];
    
        return ret;
}
思路二:分治+二分查找
題目解析:

  這個題目簡單來說就是如果將兩個已經排序的數組合併爲一個虛擬數組,求出這個虛擬數組的中位數即可。

解題思路:

  1、這道題最主要的就是切(cut),怎麼將數組切成合適的兩段是關鍵,對於一個數組來說在數組的中間將其切成兩段,這時候就要分情況討論,如果是偶數個,中位數就是切點的兩邊第一個數的平均值,如果是奇數個,中位數就是切點右邊的第一個數,比如說1 2 3 4 5,在中間的位置將這個數組切成兩段:1 2 \ 3 4 5,很顯然,中位數就是3,如果是1 2 3 4,那麼就切成了1 2 \ 3 4,很顯然中位數就是(2+3)/2 = 2.5。
  2、理解了切的思想,接下來就開始在兩個數組中進行切,這時候就用到了分治思想。
  怎麼分?
  題目中要求的時間複雜度爲 O(log(m + n)),很容易想到的方法就是二分,現在有兩個數組,要對那個數組進行二分合適?由於找的是中位數,那麼這個數字的兩邊的元素個數是相等的,所以只需要確定一個數組中的兩邊元素,兩一個數組的對應的補上去就可以了,爲了提高效率,要選擇最短的數組做二分查找。
  怎麼治?
  這個也很容易,只需要分別比較兩個數組切點兩邊的數就可以,假設數組1中切點兩邊的元素爲L1,R1,數組2中切點兩邊的元素爲:L2,R2,切完之後有三種情況:
  1)L1>R2 ,說明數組1的左半邊比數組2的右半邊大,應該讓cut向左移,才能使數組一中較多的數被分配到右邊。
  2)L2>R1 ,說明數組2的左半邊比數組1的右半邊大,應該讓cut向右移,才能使數組一中較多的數被分配到左邊。
  3)其他情況(L1<=R2 L2<=R1),cut的位置是正確的,可以停止查找,輸出結果。
3.特殊情況
分析完了正常的情況,那麼就要分析一下特殊情況;
  1)如果有一個數組是空的,直接返回另一個不爲空的數組中的中位數
  2)如果兩個數組元素的個數相等,並且兩個數組的中位數相等,直接返回其中一箇中位數。
  3)有可能在進行二分查找的時候出現了數組越界的情況,只需要定義一個最大值和一個最小值,這樣可以按照正常的情況來處理了。
4、輸出結果分情況
  兩個數組的輸出情況和之前的一個數組的輸出大致是一樣,只是添加了選擇性,如果是偶數個,輸出(min(L1, L2) + min(R1, R2))/2,如果是奇數個,輸出min(R1, R2)。

圖解思路:

在這裏插入圖片描述
  這裏只對不越界和不爲空的數組進行解析。
在這裏插入圖片描述

代碼解析:
class Solution {
public:
        double MedNum(vector<int>& num)
        {
            double ret;
            size_t size = num.size();
            if(size == 1)
                return num[0];
            int med = size / 2;
            if (size% 2 == 0)
            {
                ret =  ((double)(num[med] + num[med - 1]) / 2);
            }
            else
                ret = num[med];
            
            return ret;
        }
   
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
     {
        double ret = -1;
        
        //定義最小數和最大數
        const int BDL_MIN_ = 0x80000000;
        const int BDL_MAX_ = 0x7fffffff;
    
    	//情況一:
        //如果這兩個數組中有一個是空的,直接返回另一個的中位數即可
        if (nums1.empty())
            return MedNum(nums2);
        else if (nums2.empty())
            return MedNum(nums1);
        //情況二:
        //如果兩個數組分別的中位數相等,那麼這兩個數的中位數就是這個中位數
        int size1 = nums1.size();	//數組一的長度
        int size2 = nums2.size();	//數組二的長度
        if(size1 == size2)
        {
        	Med1 = MedNum(nums1);
        	Med1 = MedNum(nums1);
        	if(Med1 == Med2)
        		return Med1;
        }
        
        //情況三:
        //使用分治思想,二分查找方法
        int size0 = size1 + size2;	//兩個數組的總長度
    
        //爲了效率,要選擇最短的數組做二分查找,使數組一爲最短
        //如果第一個數組比第二個數組長,就要使兩個數組交換
        //但是swap的時間複雜度使O(N+M),所以將兩個數組交換位置再調一次函數即可
        if (size1 > size2)
            return findMedianSortedArrays(nums2, nums1);
    
        //設置二分查找的範圍
        int cutL = 0;
        int cutR = size1;
    
        //這裏將cut1初始爲數組1的中位數
        int cut1 = (size1 - 1) / 2;
        while (cut1 <= size1)
        {
        
            cut1 = (cutR - cutL) / 2+cutL;                           //在數組1中找cut1切點(二分)
            int cut2 = (size0 / 2) - cut1;                           //在數組2中找cut2切點
            //這裏不用size2直接確定切點,是因爲將兩個數組進行了虛擬合併,使用虛擬數組的總數size0找中間點,然後將屬於數組一的部分減去,就是cut2在數組2中的切點,可以結合上面的圖片捋一下。
            //確定L1,L2,L3,L4的值,並判斷當前的切點有沒有越界          
            double L1 = (cut1 == 0) ? BDL_MIN_ : nums1[cut1-1];
            double R1 = (cut1 == size1) ? BDL_MAX_ : nums1[cut1];
            double L2 = (cut2 == 0) ? BDL_MIN_ : nums2[cut2-1];
            double R2 = (cut2 == size2) ? BDL_MAX_ : nums2[cut2];
            
            //如果L1>R2,則cut1應該向左移,才能使數組1較多的數被分配到右邊。
            if (L1 > R2)
                cutR = cut1-1;
            //如果L2 > R1,則cut1應該向右移,才能使數組1較多的數被分配到左邊。
            else if (L2 > R1)
                cutL = cut1+1;
            //其他情況就是L1<=R2 L2<=R1,說明當前cut1和cut2的位置就是中位數的位置了
            else
            {
            	//如果兩個數組中加起來的元素數量是偶數,那麼中位數就應該是兩個中位數的平均值
                if (size0 % 2 == 0)
                {
                    L1 = max(L1, L2);
                    R1 = min(R1, R2);
                    ret = (L1 + R1) / 2;
                    return ret;
                }
                //如果兩個數組中元素的數量是奇數,中位數就是當前位置的右值
                else
                {
                    ret = min(R1, R2);
                    return ret;
                }
            }
        }
    
        return ret;
    }
};

如果有什麼問題可以在評論區留言!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章