二分(一)寻找两个有序数组的中位数

问题描述

给定两个有序的数组 \(nums1\)\(nums2\),现在要求得这两个数组元素组成的序列中的中位数

要求:算法的时间复杂度要在 \(log_2n\) 级别内

解决思路

只要遍历了数组,那么算法的时间复杂度就为 \(O(n)\),不满足要求

一般时间复杂度为 \(O(log_2n)\) 的算法实现都是基于分治策略来实现的

  • 分治

    将原问题等效为:从两个有序数组中找到第 \(k\) 小的数(\(k\) 为中位数的位置)

    首先分情况进行讨论:

    • 两个数组的总个数为偶数:找到 \(k\) 小的元素和 \(k+1\) 小的元素,结果为两个元素的平均值
    • 两个数组总个数为奇数:结果为第 \(k\) 小的元素

    具体的分治思路:

    • 默认第一个数组的长度要比第二个数组的长度要短,如果不满足这个条件则调换这两个数组

    • 第一个数组的有效长度从 \(i\) 开始,第二个数组的有效长度从 \(j\) 开始。其中 \([i, s_i - 1]\) 表示第一个数组的前 \(k/2\) 个有效元素,\([j, s_j - 1]\) 表示第二个数组的前 \(k - k/2\) 个有效长度(这么做的目的是为了确保 \(k\) 为奇数是的正确性)

    • \(nums1[s_i - 1] > nums[s_j - 1]\) 时,表示第 \(k\) 小的元素一定不在 \([j, s_j - 1]\) 区间内,可以舍弃这个区间的元素

    • \(nums1[s_i - 1] \leq nums[s_j - 1]\) 时,表示第 \(k\) 小的元素一定不在 \([i, s_i - 1]\) 区间内,可以舍弃这个区间的元素

    • 不断递归,重复上面的步骤,最终当 \(k\) 不断缩小到 \(1\) 时,就找到了第 \(k\)

  • 二分搜索

    假设两个有序数组分别是 \(A\)\(B\),现在问题转换为在 \(A\)\(B\) 两个数组中找到第 \(k\) 个元素。

    要找到第 \(k\) 个元素,可以比较 \(A[k/2 - 1]\)\(B[k/2 - 1]\)

    由于 \(A[k/2 - 1]\)\(B[k/2 - 1]\) 前分别有 \(A[0……k/2 - 2]\)\(B[0……k/2 - 2]\),即 \(k/2 - 1\) 个元素,对于 \(A[k/2-1]\)\(B[k/2 - 1]\) 中的较小值,最多只会有 \((k/2- 1) + (k/2 -1) \leq k - 2\) 个元素比它小,因此它便不是第 \(k\) 小的数了

    因此可以归纳为以下集中情况:

    • 如果 \(A[k/2 -1] < B[k/2-1]\),那么比 \(A[k/2 -1]\) 小的数只有前 \(k/2 - 1\) 个和 \(B\) 的前 \(k/2- 1\) 个数,即比 \(A[k/2 - 1]\) 小的数最多只有 \(k-2\) 个,因此 \(A[k/2 - 1]\) 不可能为第 \(k\) 个数,\(A[0]\)\(A[k/2-1]\) 也都不可能是第 \(k\) 个数,可以全部排除
    • 如果 \(A[k/2 - 1] > B[k/2 - 1]\) ,则可以排除 \(B[0]\)\(B[k/2 - 1]\)
    • 如果 \(A[k/2 - 1] = B[k/2 - 1]\) 可以归纳到第一种情况进行处理

    存在的特殊情况:

    • 如果 \(A[k/2 - 1]\) 或者 \(B[k/2 - 1]\) 越界,那么可以选择对应数组中的最后一个元素,在这种情况下必须根据排除的元素的个数减少 \(k\) 的值,而不能直接将 \(k\) 减去 \(k/2\)
    • 如果一个数组为空,说明这个数组中的所有的元素都已经被排除完毕了,此时可以直接返回另一个数组中第 \(k\) 小的元素
    • 如果 \(k = 1\) ,那么只需要返回两个数组首元素的最小值即可

    该实现思路与上文使用分治的思路是相对应的

实现

  • 分治

    class Solution {
        public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            int total = nums1.length + nums2.length;
            
            // 元素个数为偶数的情况
            if (total % 2 == 0) {
                int left = find(nums1, 0, nums2, 0, total / 2);
                int right = find(nums1, 0, nums2, 0, total / 2 + 1);
                return (left + right) / 2.0;
            }
    
            return find(nums1, 0, nums2, 0, total / 2 + 1);
        }
    
        int find(int[] nums1, int i, int[] nums2, int j, int k) {
            if (nums1.length - i > nums2.length - j) // 如果第一个数组的长度大于第二个数组的长度,则交换它们
                return find(nums2, j, nums1, i, k);
            
            if (i >= nums1.length) // nums1 数组已经到达结尾,因此直接从 nums2 中获取对应的元素
                return nums2[j + k - 1];
    
            if (k == 1) // 终止条件
                return Math.min(nums1[i], nums2[j]);
    
            int si = Math.min(i + k / 2, nums1.length), sj = j + k - k / 2;
            if (nums1[si - 1] > nums2[sj - 1])
                return find(nums1, i, nums2, sj, k - sj + j);
    
            return find(nums1, si, nums2, j, k - si + i);
        }
    }
    

    复杂度分析:

    ​ 时间复杂度:\(O(log_2n)\)

    ​ 空间复杂度:忽略由于递归调用带来的开销,空间复杂度为 \(O(1)\)

  • 二分搜索

    class Solution {
        public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            int n1 = nums1.length, n2 = nums2.length;
            int total = n1 + n2;
    
            if (total % 2 == 0)
                return (getKthElement(nums1, nums2, total / 2) 
                        + getKthElement(nums1, nums2, total / 2 + 1)
                        ) / 2.0;
            
            return getKthElement(nums1, nums2, total / 2 + 1);
        }
    
        int getKthElement(int[] nums1, int[] nums2, int k) {
            int n1 = nums1.length, n2 = nums2.length;
            int idx1 = 0, idx2 = 0;
    
            while (true) {
                if (idx1 == n1) 
                    return nums2[idx2 + k - 1];
                if (idx2 == n2) 
                    return nums1[idx1 + k -1];
                if (k == 1)
                    return Math.min(nums1[idx1], nums2[idx2]);
                
                int half = k / 2;
                int newIdx1 = Math.min(idx1 + half, n1) - 1;
                int newIdx2 = Math.min(idx2 + half, n2) - 1;
                
                int pivot1 = nums1[newIdx1], pivot2 = nums2[newIdx2];
    
                if (pivot1 <= pivot2) {
                    k -= (newIdx1 - idx1 + 1);
                    idx1 = newIdx1  + 1;
                } else {
                    k -= (newIdx2 - idx2 + 1);
                    idx2 = newIdx2 + 1;
                }
            }       
        }
    }
    

    复杂度分析:

    ​ 时间复杂度:\(O(log_2n)\)

    ​ 空间复杂度:\(O(1)\)

参考:

[1]https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/

[2]https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/shua-chuan-lc-po-su-jie-fa-fen-zhi-jie-f-wtu2/

posted @ 2021-11-07 16:00  FatalFlower  阅读(88)  评论(0编辑  收藏  举报