▶ 问题:求两个已经排好序的数组的中位数。
▶ 简单的归并版。现将两个数组一趟归并到一个数组中(O(m+n)),再利用新数组长度的奇偶性计算新数组的中位数(O(1)),总体时间复杂度 O(m+n),空间复杂度 O(m+n)。
● 代码,79 ms
1 class Solution // O(m+n) 2 { 3 public: 4 double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) 5 { 6 std::vector<int>temp; 7 int i, j; 8 for (i = 0, j = 0; i < nums1.size() && j < nums2.size();) // merge common part 9 temp.push_back(nums1[i] < nums2[j] ? nums1[i++] : nums2[j++]); 10 if (i < nums1.size() || j < nums2.size()) // merge rest part 11 { 12 std::vector<int> & ref = (i < nums1.size()) ? nums1 : nums2; 13 for (i = (i < nums1.size()) ? i : j; i < ref.size(); temp.push_back(ref[i++])); 14 } 15 if (temp.size() % 2) // nums1.size() + nums2.size() is odd 16 return (double)temp[temp.size() / 2]; 17 else // even 18 return ((double)temp[temp.size() / 2] + temp[temp.size() / 2 - 1]) / 2; 19 } 20 };
▶ 高端的分组分段法,时间复杂度 O(log(min(m, n))),空间复杂度 O(1) 。精彩的算法解释原文:https://leetcode.com/problems/median-of-two-sorted-arrays/solution/
● 算法说明(仅说明方法,不按照代码逻辑的顺序)
① 先调整两个数组 nums1 和 nums2,使得 nums1.size() ≤ nums2.size()。可以理解为,当我们吧两个数组直接拼接在一起之后,要保证位于两数组总长一半位置的元素一定处于第二个数组中。我们记调整后两数组的长度分别为 m 和 n,m ≤ n 。
② 考虑一种极端情况,nums1 的最大值比 nums2 的最小值还要小(nums1[ m - 1 ] < nums2[ 0 ])。则两个数组拼在一起相当于得到了一个已经排好序的新数组,此时所求中位数为 nums2[ ( n - m - 1 ) / 2 ](当 m + n 为奇数,此时必有 n ≥ m + 1)或 ( nums1[ m - 1 ] + nums2[ 0 ] ) / 2(当 m == n)或 ( nums2[ ( n - m ) / 2 - 1 ] + nums2[ ( n - m ) / 2 ] ) / 2(当 m + n 为偶数,且 n ≥ m + 2)。
③ 考虑另一种极端情况,nums1 的最小值比 nums2 的最大值还要大(nums1[ 0 ] < nums2[ n - 1 ])。则中位数应该是 nums2[ ( n + m ) / 2 ] (当 m + n 为奇数,此时必有 n ≥ m + 1)或 ( nums1[ 0 ] + nums2[ n-1 ] ) / 2(当 m == n)或 ( nums2[ ( m + n ) / 2 - 1 ] + nums2[ ( m + n ) / 2 ] ) / 2(当 m + n 为偶数,且 n ≥ m + 2)。
④ 除去上面两种情况,就剩下了两数组元素的值有交叉部分的情况,此时我们希望寻找“最中间”的一个或两个数。相当于使用两个下标 k1(0 ≤ k1 < m)和 k2(0 ≤ k2 < n)将原数组分别切分、重组为两个长度差不多的部分,那么所求中位数就应该在切分的断面上去找。设切分的两个数组为 left = {nums1[ i ],nums2[ j ],i = D1k1 - 1,j = D1k2 - 1},right = {nums1[ i ],nums2[ j ],i = Dk1m - 1,j = Dk2n - 1},满足 left.size() == right.size() 或 left.size() == right.size() - 1,那么所求中位数等于 ( max( left ) + min( right ) ) / 2 或 min( right ) 。
⑤ 寻找 k1 和 k2 的过程可以描述为,求 k1 和 k2,满足:❶ k1 + k2 == ( m + n + 1 ) / 2;❷ nums1[ k1 - 1 ] ≤ nums2[ k2 ] 且 nums2[ k2 - 1] ≤ nums1[ k1 ] 。
⑥ 作几点说明:
❶ 条件一保证位置对称,+1 是为了合并 m 和 n 的奇偶情况;
❷条件二 保证值卡在两数组的交点上,可以进一步理解为,若 nums1[ k1 ] < nums2[ k2 - 1 ],则说明 k1 偏小(k1跑到了前半段中去);若 nums1[ k1 - 1 ] > nums2[ k2 ],则说明 k2 偏小(k2 跑到了前半段中去),从而指明接下来 k1 和 k2 的搜索方向;
❸搜索过程可以令 k1 = 0,调整 k2 = ( m + n + 1 ) / 2 - k1,通过不断递增 k1 来搜索满足第二个条件的 k1 和 k2 的值,在后面的代码中还使用了二分查找来优化;
❹ 条件二中同时使用了下标 k1、k2、k1 - 1、k2 - 1,将前面两种极端情况独立出去就是为了防止访问越界。应当指明,当 0 ≤ k1 < m 时,k2 = ( m + n + 1 ) / 2 - k1 > ( m + n + 1 ) / 2 - m = ( n - m - 1) / 2 ≥ 0,同时 k2 = ( m + n + 1 ) / 2 - k1 ≤ ( m + n + 1 ) / 2 - 0 ≤ ( n + n + 1 ) / 2 < n,k1、k2 边界十分巧妙,只要判断其中一个就够了。
● 代码,25 ms,其中的注释阐明了部分极端情况的分支。蜜汁优化 static const auto _____ = []() {ios::sync_with_stdio(false); cin.tie(nullptr); return nullptr; }(); 后15 ms 。
1 class Solution // O(log(min(m,n))) 2 { 3 public: 4 double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 5 { 6 int m = nums1.size(), n = nums2.size(); 7 if (m == 0 && n == 0) 8 exit(EXIT_FAILURE); 9 if (m > n) 10 { 11 vector<int> temp = nums1; 12 nums1 = nums2; 13 nums2 = temp; 14 m = nums1.size(); 15 n = nums2.size(); 16 } 17 if (m == 0) 18 return (n % 2) ? nums2[n / 2] : ((double)nums2[n / 2] + nums2[n / 2 - 1]) / 2; 19 20 int left = 0, right = m, i, j; 21 for (i = (left + right) / 2, j = (m + n + 1) / 2 - i;; i = (left + right) / 2, j = (m + n + 1) / 2 - i) 22 { 23 if (i < right && nums1[i] < nums2[j - 1]) // nums1[i] is too small 24 left = i + 1; 25 else if (i > left && nums2[j] < nums1[i - 1]) // nums2[j] is too small 26 right = i; 27 else 28 break; 29 } 30 31 int max_left, min_right; 32 if (i == 0) // nums1[0] > nums2[n-1], both max_left and min_right are in nums2 because m <= n 33 max_left = nums2[j - 1]; 34 else if (j == 0) // nums1[m-1] < nums2[0] && m == n, return (nums1[m-1] + nums2[0])/2 35 max_left = nums1[i - 1]; 36 else 37 max_left = (nums1[i - 1] > nums2[j - 1]) ? nums1[i - 1] : nums2[j - 1]; 38 39 if ((m + n) % 2) 40 return max_left; 41 42 if (i == m) // nums1[m-1] < nums2[0], both max_left and min_right are in nums2 because m <= n 43 min_right = nums2[j]; 44 else if (j == n) // nums1[0] > nums2[n-1] && m == n, return (nums1[0] + nums2[n-1])/2 45 min_right = nums1[i]; 46 else 47 min_right = (nums1[i] < nums2[j]) ? nums1[i] : nums2[j]; 48 49 return (max_left + min_right) / 2.0; 50 } 51 };