【LeetCode刷題記錄】4. 尋找兩個有序數組的中位數

題目描述:
在這裏插入圖片描述
一個最簡單、最容易想到的方法是:將兩個數組排序、存入同一個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)。

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