4. 寻找两个正序数组的中位数

给定两个大小为 mn 的正序(从小到大)数组 nums1nums2

请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))

你可以假设 nums1nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

解答

下面这个复杂度是O(m+n),勉强过了:

class Solution {
     
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
     
        vector<double> temp;
        int m = nums1.size();
        int n = nums2.size();
        int i=0, j=0;
        // 既然两个数组都排过序了,直接遍历中间即可
        while((i+j) <= (m+n)/2){
     
            if(i == m){
     
                temp.push_back(nums2[j]);
                j++;
                continue;
            }
            if(j == n){
     
                temp.push_back(nums1[i]);
                i++;
                continue;
            }
            if(nums1[i] < nums2[j]){
     
                temp.push_back(nums1[i]);
                i++;
            }
            else{
     
                temp.push_back(nums2[j]);
                j++;
            }
        }
        int l = temp.size();
        if((m+n)%2==0)
            return (temp[l-2] + temp[l-1]) / 2;
        else
            return temp[l-1];
    }
};

参考讨论区,结合二分搜索,实现题目要求复杂度:

class Solution {
     
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
     
        int m = nums1.size();
        int n = nums2.size();
        // 当m+n为奇数时,target1=target2,当为偶数时,正好包含两个中位数
        int target1 = (m+n+1)/2;
        int target2 = (m+n+2)/2;
        return (helper(nums1, nums2, 0, 0, target1) + helper(nums1, nums2, 0, 0, target2)) / 2.0;
    }
    //查找两个有序数组中的第target个排序数字
    int helper(vector<int>& nums1, vector<int>& nums2, int start1, int start2, int target){
     
    	// 其中一个数组为空
        if(start1>=nums1.size())
            return nums2[start2+target-1];
        if(start2>=nums2.size())
            return nums1[start1+target-1];
        if(target==1)
            return min(nums1[start1], nums2[start2]);
        // 二分,各在两个数组中查找一半,可能数组长度小于目标排序的一半
        int mid1 = (start1 + target/2 - 1) < nums1.size() ? nums1[start1+target/2-1] : INT_MAX;
        int mid2 = (start2 + target/2 - 1) < nums2.size() ? nums2[start2+target/2-1] : INT_MAX;

        if(mid1 < mid2)
            return helper(nums1, nums2, start1+target/2, start2, target-target/2);
        else
            return helper(nums1, nums2, start1, start2+target/2, target-target/2); 
    }
};

还有一种切割的思路,假设nums1的长度小于nums2,将两数组合并后的有序数组,中位数位置是(m+n+1)/2,设在合并数组的中位数前,总共有原nums1数组k个数字,则相应有nums2数组(m+n+1)/2-k个数字,转化为在nums1中寻找这样的切割点k,使得nums1切割点左侧的数字小于nums2对应切割点右侧的数字,同时nums2切割点左侧的数字同样小于nums1切割点左侧的数字,(由于原来的两个数组都是排序的,因此同一个数组切割点左侧数字一定小于右侧数字),这样就使得两数组切割点左侧总共有(m+n+1)/2个数字了,并且均小于切割点右侧数字,对应于中位数。

class Solution {
     
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
    {
     
        // 1. 保持num1是长度较短的数组
        if (nums1.size() > nums2.size()) return findMedianSortedArrays(nums2, nums1);

        const int m = nums1.size();
        const int n = nums2.size();

        int left=0, right=m, lmax1, lmax2, rmin1, rmin2;
        while(left <= right)
        {
     
            // 2. 从 num1 中取一个 i 位置 
            int i = (left+right)/2;

            // 3. 根据中位数性质,此时 num2 应取在 j 处,才可能让nums1+nums2左右两侧相等
            int j = (m + n + 1)/2 - i;

            // 4. 按照当前的划分,将分割线周围的4个元素都取出,准备进行比较,调整区间
            lmax1 = i == 0? INT_MIN : nums1[i-1];
            lmax2 = j == 0? INT_MIN : nums2[j-1];
            rmin1 = i >= m? INT_MAX : nums1[i];
            rmin2 = j >= n? INT_MAX : nums2[j];

            // 5.1 nums1的左边最大 大于 nums2的右边最小,因此 i 取大了,往前移动
            if (lmax1 > rmin2) right = i-1;
            // 5.2 nums2的左边最大 大于 nums1的右边最小,因此 i 取小了,往后移动
            else if (lmax2 > rmin1 ) left = i+1;
            // 5.3 四个数已经平衡,可以结束
            else break;
        }

        if ((m + n) % 2 == 0)
        {
     
            return (max(lmax1, lmax2)+min(rmin1, rmin2))/2.0;
        }
        else 
        {
     
            return max(lmax1, lmax2);
        }
    }
};

你可能感兴趣的:(LeetCode)