4. Median of Two Sorted Arrays

我们两清 提交于 2020-03-27 17:00:58

▶ 问题:求两个已经排好序的数组的中位数。

▶ 简单的归并版。现将两个数组一趟归并到一个数组中(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 };

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!