請找出這兩個有序數組的中位數。要求算法的時間復雜度為 O(log (m+n)) 。
示例 1:
nums1 = [1, 3] nums2 = [2] 中位數是 2.0
示例 2:
nums1 = [1, 2] nums2 = [3, 4] 中位數是 (2 + 3)/2 = 2.5
自己的思路:既然兩個數組都是有序的,我可以將兩個數組合並成一個數組,依然有序,然后根據奇偶來判斷出中位數。
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { vector<int>nums; int i=0,j=0; while(i<nums1.size() && j<nums2.size()) { if(nums1[i]<nums2[j]) { nums.push_back(nums1[i++]); } else { nums.push_back(nums2[j++]); } } if(i==nums1.size()) { while(j<nums2.size()) nums.push_back(nums2[j++]); } else if(j==nums2.size()) { while(i<nums1.size()) nums.push_back(nums1[i++]); } if(nums.size()%2) return nums[nums.size()/2]; else { return (double)(nums[nums.size()/2]+nums[nums.size()/2-1])/2; } }
但是這個算法很明顯的不符合題目中的算法復雜度的要求,所以整理了他人的優秀實現方法
(1)中位數,其實就是找到第k個大小的元素的特例。在單數組中實現方式簡單,關鍵是如何在兩個數組中找到第k大的元素。
難就難在要在兩個未合並的有序數組之間使用二分法,這里我們需要定義一個函數來找到第K個元素,由於兩個數組長度之和的奇偶不確定,因此需要分情況來討論,對於奇數的情況,直接找到最中間的數即可,偶數的話需要求最中間兩個數的平均值。下面重點來看如何實現找到第K個元素,首先我們需要讓數組1的長度小於或等於數組2的長度,那么我們只需判斷如果數組1的長度大於數組2的長度的話,交換兩個數組即可,然后我們要判斷小的數組是否為空,為空的話,直接在另一個數組找第K個即可。還有一種情況是當K = 1時,表示我們要找第一個元素,只要比較兩個數組的第一個元素,返回較小的那個即可。
首先假設數組A和B的元素個數都大於k/2,我們比較A[k/2-1]和B[k/2-1]兩個元素,這兩個元素分別表示A的第k/2小的元素和B的第k/2小的元素。這兩個元素比較共有三種情況:>、<和=。如果A[k/2-1]大於B[k/2-1],則A[k/2-1]小於合並之后的第k小值。
證明也很簡單,可以采用反證法。假設A[k/2-1]大於合並之后的第k小值,我們不妨假定其為第(k+1)小值。由於A[k/2-1]小於B[k/2-1],所以B[k/2-1]至少是第(k+2)小值。但實際上,在A中至多存在k/2-1個元素小於A[k/2-1],B中也至多存在k/2-1個元素小於A[k/2-1],所以小於A[k/2-1]的元素個數至多有k/2+ k/2-2,小於k,這與A[k/2-1]是第(k+1)的數矛盾。
同理當A[k / 2 - 1] > B[k / 2 -1]時存在類似的結論
當A[k / 2 - 1] = B[k / 2 -1]時,表示,在在A的k/2 -1之前已經有k/2 -1和數小於A[k / 2 -1],同理在B 之前也是一樣的,所以此時已經找到了第k小的數,即這個相等的元素。
double findKth(vector<int> &nums1, int i, vector<int> &nums2, int j, int k) { // 首先需要讓數組1的長度小於或等於數組2的長度 if (nums1.size() - i > nums2.size() - j) { return findKth(nums2, j, nums1, i, k); } // 判斷小的數組是否為空,為空的話,直接在另一個數組找第K個即可 if (nums1.size() == i) { return nums2[j + k - 1]; } // 當K = 1時,表示我們要找第一個元素,只要比較兩個數組的第一個元素,返回較小的那個即可 if (k == 1) { return min(nums1[i], nums2[j]); } int pa = min(i + k / 2, int(nums1.size())), pb = j + k - pa + i; if (nums1[pa - 1] < nums2[pb - 1]) { return findKth(nums1, pa, nums2, j, k - pa + i); } else if (nums1[pa - 1] > nums2[pb - 1]) { return findKth(nums1, i, nums2, pb, k - pb + j); } else { return nums1[pa - 1]; } } double findMedianSortedArrays(vector<int> A, vector<int> B) { int sizeA = A.size(), sizeB = B.size(); if (sizeA <= 0 && sizeB <= 0) { return 0; } int total = sizeA + sizeB; if (total % 2 == 1) { return findKth(A, 0, B, 0, total / 2 + 1); } else { return (findKth(A, 0, B, 0, total / 2) + findKth(A, 0, B, 0, total / 2 + 1)) / 2; } }
這里比較難理解的點是判斷(nums1[pa - 1] < nums2[pb - 1])之后執行了return findKth(nums1, pa, nums2, j, k - pa + i);其實這個操作是因為目前nums1的分界線的值小於nums2分界線的值,那么證明nums1分界線以及前面的值都小於合並后的第k的值,也就是中位數。那么我們可以從這里開始,繼續尋找第k-(pa-i)的值,直到兩個值相等為止。
(2)分治法。
https://hk029.gitbooks.io/leetbook/%E5%88%86%E6%B2%BB/004.%20Median%20of%20Two%20Sorted%20Arrays[H]/004.%20Median%20of%20Two%20Sorted%20Arrays[H].html
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { if(nums1.size() == 0) return MedofArray(nums2); if(nums2.size() == 0) return MedofArray(nums1); int n = nums1.size(); int m = nums2.size(); if(n > m) //保證數組1一定最短 return findMedianSortedArrays(nums2,nums1); int L1,L2,R1,R2,c1,c2,lo = 0, hi = 2*n; //我們目前是虛擬加了'#'所以數組1是2*n+1長度 while(lo <= hi) //二分 { c1 = (lo+hi)/2; //c1是二分的結果 c2 = m+n- c1; L1 = (c1 == 0)?INT_MIN:nums1[(c1-1)/2]; //map to original element R1 = (c1 == 2*n)?INT_MAX:nums1[c1/2]; L2 = (c2 == 0)?INT_MIN:nums2[(c2-1)/2]; R2 = (c2 == 2*m)?INT_MAX:nums2[c2/2]; if(L1 > R2) hi = c1-1; else if(L2 > R1) lo = c1+1; else break; } return (max(L1,L2)+ min(R1,R2))/2.0; } double MedofArray(vector<int>& nums) { if(nums.size() == 0) return -1; return (nums[nums.size()/2]+nums[(nums.size()-1)/2])/2.0; }
需要細細思考下