4. Median of Two Sorted Arrays

题意

给两个有序数组,找到两个数组合并之后的中位数。
时间复杂度要求限制在O(log (m+n))

思路

步骤1

看到这个时间复杂度,就能想到应该使用二分的方法去解决。
这个题目其实就是两个有序数组中,查询第K小的元素的问题。为了避免奇偶的问题,有如下小trick:

长度为n的一列有序数,中位数为:
x = (arr[(n-1)/2]+arr[n/2])/2 (假设数组arr第一个存储的位置为0)
x = (arr[(n+1)/2]+arr[(n+2)/2])/2 (假设数组arr第一个存储的位置为1)
当元素个数为偶数个时,第arr[(n+1)/2]个数和第arr[(n+2)/2]个数分别是两个待取的元素
当元素个数为奇数个时,第arr[(n+1)/2]个数和第arr[(n+2)/2]个数是同一个,即为待取的中位数

所以本题中,我们只需要找到两个数组中,第arr[(n+1)/2]小的数和第arr[(n+2)/2]小的数即可。
那么如何在两个有序数组中,查询第K小的元素呢?

步骤2

首先考虑一般情况,如图
4. Median of Two Sorted Arrays_第1张图片
假设阴影部分包含了两个有序数组的前k个数,很显然前,第k个数就在k1或者k2中。根据我们前面提到的,要使用二分法来解题,那么,我们每次将阴影部分的区域减小一半,即前k个数不断缩小范围,最后就可以收敛到k1或者k2了。
当然,k是没法直接直白地除以2的,那么缩小阴影区域的面积,就要在st1,st2,即阴影部分的两个起点做手脚了,具体做法是:
我每次让st1或是st2中的一个,向右移动k/2这么多个位置,并且让k每次减小k/2,最后即可收敛到只剩一个数!
第一次,我在阴影部分里找到第K个数。
第二次,我在减小范围后的阴影部分里找到这个部分里找第k-k/2个数。
第三次,……
最后,阴影部分的区域大小,即K,一定会收敛到1,此时,我们就找到了第K个数。

步骤3

相信看了上面的描述,你至少会有了一定的感觉。
剩下的问题是,到底是st1移动还是st2移动呢?
首先明确一点,不论是st1移动还是st2移动,我们删去的阴影区域,都是不可能存在第K个数的区域,那么哪个区域不可能存在第K个数呢?
我们比较一下 nums1[st1+k/2-1] 和 nums2[st2+k/2-1]这两个数,如果前者小,说明在nums1中,从第st1到第st1+k/2-1这个区域中,必然不可能存在第K个数。
4. Median of Two Sorted Arrays_第2张图片
因为 st1——st1+k/2-1 和 st2——st2+k/2-1 两个区间的长度都为k/2。这两个区域长度之和必然为K或K-1(因为有奇偶的问题)。
如果nums1[st1+k/2-1]小一点,即nums1中前k/2个数中最大的那个数都比nums2[st2+k/2-1]小。那么这个区域内肯定没有一个数比nums2[st2+k/2-1]大。那么第K个数再怎么地,也不可能是 st1——st1+k/2-1 这个区间对吧。这时,st1就可以+=k/2,抛弃掉这块不可能的区间了。
同时,k要减去k/2,因为在剩下的两个数列中,你只需要找第k-k/2个数即可。

步骤4

最后,处理一些边界值数据即可。比如,st1+k/2-1这个下标是可以越界的,越界的时候,我们为了代码的简洁,不抽取 nums1[st1+k/2-1] 或 nums2[st2+k/2-1]的值,而是直接赋INT_MAX的值。
原因是这样的,如果越界了,说明st1不能再往右移动k/2这么多个元素了,那么只能让st2去移动。
我当时非常担心,有可能st2移动的这一部分会抛弃掉第K个数,因为st1是走了后门——长度不够才让st2移动的。
其实无需担心,你这样想,把st1这一段不够k/2这么长的区域,全部补在nums2的最左边,即假设这段不到k/2长度的区域中,每个数都小于nums2还留着的阴影部分的最左端(即最小值),但哪怕是这样,第K个数也不会被丢掉,因为这段不到k/2长度的区域的长度加上k/2这么长,仍然够不到K,即第K个数不会被抛弃,st2就可以大大方方地向右移动k/2。

代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
    {
        int n1 = nums1.size(), n2 = nums2.size();
        int r1 = (n1 + n2 + 1) / 2, r2 = (n1 + n2 + 2) / 2;
        return (findK(nums1, nums2, 0, 0, r1) + findK(nums1, nums2, 0, 0, r2)) / 2.0;
    }
    int findK(vector<int>& nums1, vector<int>& nums2, int st1, int st2, int k)
    {
        while (k>1)
        {
            int temp1 = (st1+k/2-1 < nums1.size())?nums1[st1+k/2-1]:INT_MAX;
            int temp2 = (st2+k/2-1 < nums2.size())?nums2[st2+k/2-1]:INT_MAX;
            
            if (temp1 < temp2) st1 += k/2;
            else st2 += k/2;
            
            k -= k/2;
        }
        if (st1 == nums1.size()) return nums2[st2];
        if (st2 == nums2.size()) return nums1[st1];
        //st1和st2一定不会大于nums1.size()和nums2.size()
        //如果你不放心,写>=也可
        return min(nums1[st1], nums2[st2]);
    }
};

参考

这题还是蛮难的,反正我是不会做。嘻嘻嘻
https://www.cnblogs.com/grandyang/p/4465932.html
https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2471/very-concise-ologminmn-iterative-solution-with-detailed-explanation

你可能感兴趣的:(leetcode)