• 4. Median of Two Sorted Arrays


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

    ▶ 简单的归并版。现将两个数组一趟归并到一个数组中(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 };
  • 相关阅读:
    org.json里实现XML和JSON之间对象互转
    Rhino-- JavaScript
    XStream -- a simple library to serialize objects to XML and back again
    [Groovy]转:Groovy 通过 isCase 方法进行分类
    [Groovy]static typing
    JavaScript Succinctly 读后笔记
    [Groovy] Private fields and methods are not private in groovy
    Android 学习之路和App开发框架
    Android自定义xml解析
    Android 动态生成对话框和EditText
  • 原文地址:https://www.cnblogs.com/cuancuancuanhao/p/8094443.html
Copyright © 2020-2023  润新知