題目描述:
一個最簡單、最容易想到的方法是:將兩個數組排序、存入同一個vector,計算中位數,如:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
vector<int> nums(n + m);
//int* nums = new int[n + m];
if (n == 0) {
if (m % 2)return nums2[m / 2];
else return (nums2[m / 2 - 1] + nums2[m / 2]) / 2.0;
}
if (m == 0) {
if (n % 2)return nums1[n / 2];
else return (nums1[n / 2 - 1] + nums1[n / 2]) / 2.0;
}
int count = 0, i = 0, j = 0;
while (count < (n + m)) {
if (i == n) {
while (j < m) {
nums[count++] = nums2[j++];
}
break;
}
if (j == m) {
while (i < n) {
nums[count++] = nums1[i++];
}
break;
}
if (nums1[i] < nums2[j])nums[count++] = nums1[i++];
else nums[count++] = nums2[j++];
}
if (count % 2)return nums[count / 2];
else return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
}
複雜度分析:遍歷兩個數組,時間複雜度爲O(m+n);創建了一個數組,空間複雜度爲O(m+n)。題目要求時間複雜度爲O(log(m+n)),顯然此方法不符合要求。
關於上述方法,實際上不需要排序、存儲兩數組,對於m+n爲奇數的,只需要知道兩數組中第(m+n)/2+1小的元素就可以了;偶數時,知道第(m+n)/2和第(m+n)/2+1個元素就可以了。所以,有:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int len = n + m, l = -1, r = -1, idxn = 0, idxm = 0;
for (int i = 0; i <= len / 2; i++) {
l = r;
if (idxn < n && (idxm >= m || nums1[idxn] < nums2[idxm]))
r = nums1[idxn++];
else
r = nums2[idxm++];
}
if (len % 2)return r;
else return (l + r) / 2.0;
}
此時,由於需要遍歷前(m+n)/2個元素,時間複雜度爲O(m+n);沒有創建新的數組,空間複雜度爲O(1)。顯然,時間複雜度仍不滿足題目要求。
題解:
一、二分法
觀察題目要求的時間複雜度O(log(m+n)),看到log第一時間想到二分法。
假設我們要找第k小的元素,分別在兩數組中找到第k/2個元素,比較當前兩元素,小的那個元素所在的數組的前k/2個元素一定不是要找的,故排除;此時,令k=k-k/2,問題轉化爲,尋找第k(=k-k/2)小的元素,反覆應用上面思路,直至k=1,則問題解決。
一個特例,假設其中一個數組的長度小於k/2,只需要將這個數組的最後一個元素拿出來比較就可以了。
奇偶數?奇數時:尋找第(m+n)/2+1小的元素;偶數時,尋找第(m+n)/2和第(m+n)/2+1小的元素。
遞歸實現
double findMedianSortedArraysDiv(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int len = n + m;
if (len % 2)
return getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1); //Kth
else
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1)) / 2.0;
}
int getKth(vector<int>& nums1, int s1, int e1, vector<int>& nums2, int s2, int e2, int k) {
int len1 = e1 - s1 + 1;
int len2 = e2 - s2 + 1;
if (len1 > len2)return getKth(nums2, s2, e2, nums1, s1, e1, k);
if (!len1)return nums2[s2 + k - 1];
if (k == 1)return nums1[s1] < nums2[s2] ? nums1[s1] : nums2[s2];
int k1 = len1 < k / 2 ? len1 : k / 2;
int k2 = len2 < k / 2 ? len2 : k / 2;
int p1 = s1 + k1 - 1;
int p2 = s2 + k2 - 1;
if (nums1[p1] < nums2[p2])
return getKth(nums1, p1 + 1, e1, nums2, s2, e2, k - k1);
else
return getKth(nums1, s1, e1, nums2, p2 + 1, e2, k - k2);
}
複雜度分析:原問題規模爲k=(m+n)/2+1,每排除一次,規模減少k/2。故2^x=k,需要x=logk=log(m+n/2)次循環,時間複雜度爲O(log(m+n));由於遞歸爲尾遞歸,編譯器沒有額外的堆棧操作,空間複雜度爲O(1)。
二、“割”
這裏引用討論區解釋,具體思路見代碼。
將兩數組劃分爲:
double findMedianSortedArraysCut(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) {
return findMedianSortedArraysCut(nums2, nums1);
}
int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n;
while (lo <= hi) {
c1 = (lo + hi) / 2; //start:c1=n(/2)
c2 = m + n - c1; //c2=m(/2),nL == nR
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}
複雜度分析:首次查找區間爲[0,n],其中n=min(n,m),該區間的長度在每次循環之後都會減少爲原來的一半。因此,時間複雜度爲O(log(min(n,m)));只需要恆定的內存來存儲幾個局部變量,因此,空間複雜度爲O(1)。