【需要理解】Leetcode 4 寻找两个正序数组的中位数

题目描述

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

来源:力扣(LeetCode)题目链接
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解一

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        auto size1 = nums1.size();
        auto size2 = nums2.size();
        //有空数组的情况,避免访问出错
        if(size1 > 0 && size2 > 0){
        //两数组可以直接相连的情况(注意-size1还是size2)
            if(nums1.back() <= nums2.front() || nums1.front() >= nums2.back() ){
            //num2中的数值大于num1
                if(nums1.back() <= nums2.front()){
                //偶数个
                    if(((size1 + size2) & 1) == 0){
                    //如果num1的元素数大于num2,说明
                    // num1+num2 < 2*num1,即两个待求数在num1中
                    //这里证明: 一奇一偶加和为奇 所以如果加和是偶数必然是两个奇数或两个偶数,所以size1和size2 至少差2,所以能确定在某一个数组中,而不是分散开
                    //如果size1 > size2,则在num1中
                    //如果size1 = size2 则在num1和num2中
                    //如果size1 < size2 则在num2中
                        if(size1 > size2){
                            return (double)((double)nums1[(size1+size2)/2]+(double)nums1[(size1+size2)/2-1])/(double)2;
                        }else if(size1 == size2){
                            return (double)((double)nums1[size1-1]+(float)nums2[0])/(double)2;
                        }else{
                            return (double)((double)nums2[(size1+size2)/2-size1]+(double)nums2[(size1+size2)/2-1-size1])/(double)2;
                        }
                    }else{
                    //奇数比较简单,所求数在size大的数组中
                        if(size1 > size2){
                            return (double)nums1[(size1+size2)/2];
                        }else if(size1 < size2){
                            return (double)nums2[(size1+size2)/2-size1];
                        }
                    }
                }
                if(nums2.back() <= nums1.front()){
                    if(((size1 + size2) & 1) == 0){
                        if(size1 > size2){
                            return (double)((double)nums1[(size1+size2)/2-size2]+(double)nums1[(size1+size2)/2-1-size2])/(double)2;
                        }else if(size1 == size2){
                            return (double)((double)nums2[size2-1]+(double)nums1[0])/(double)2;
                        }else{
                            return (double)((double)nums2[(size1+size2)/2]+(double)nums2[(size1+size2)/2-1])/(double)2;
                        }
                    }else{
                        if(size1 > size2){
                            return (double)nums1[(size1+size2)/2-size2];
                        }else if(size1 < size2){
                            return (double)nums2[(size1+size2)/2];
                        }
                    }
                }
            }else{
            //如果是两数组穿插合并
                double temp = 0, maxx = 0;
                int j = 0, f = 0;
                for(int i = 0; i <= (size1+size2)/2; i++){
                    //temp是倒数第二个数
                    //max是倒数第一个数
                    //注意越界
                    temp = maxx;
                    if(j < size1 && f < size2)
                        maxx = nums1[j] <= nums2[f] ? nums1[j++]:nums2[f++];
                    else if(j >= size1)
                        maxx = nums2[f++];
                    else maxx = nums1[j++]; 
                }
                //如果是偶数
                if(((size1+size2) & 1) == 0) return (double)(temp+maxx)/(double)2;
                else return maxx;
            }
            //如果有一方无元素
        }else if (size1 == 0){
            if(! (size2 & 1)){
                return ((double)nums2[size2/2] + (double)nums2[size2/2-1]) /(double) 2;
            }else return (double)nums2[size2/2];
        }else if (size2 == 0){
            if(! (size1 & 1)){
                return ((double)nums1[size1/2] + (double)nums1[size1/2-1]) /(double) 2;
            }else return (double)nums1[size1/2];
        }
        //leetcode强制return
        return 0.00000;
    }
};

提交结果

题解二(二分法)(参考代码)

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
    {
        int m = nums1.size();
        int n = nums2.size();
        //中位数 = (left + right)/2
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }
    //在两个有序数组中找到第k个元素(例如找第一个元素,k=1,即nums[0])
    //i: nums1的起始位置 j: nums2的起始位置(i,j都是从0开始)
    int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k)
    {
        //若nums1为空(或是说其中数字全被淘汰了)
        //在nums2中找第k个元素,此时nums2起始位置是j,所以是j+k-1
        if(i >= nums1.size())    return nums2[j + k - 1];
        //nums2同理
        if(j >= nums2.size())    return nums1[i + k - 1];

        //递归出口
        if(k == 1)  return std::min(nums1[i], nums2[j]);

        //这两个数组的第K/2小的数字,若不足k/2个数字则赋值整型最大值,以便淘汰另一数组的前k/2个数字
        int midVal1 = (i + k/2 - 1 < nums1.size()) ? nums1[i + k/2 - 1] : INT_MAX;
        int midVal2 = (j + k/2 - 1 < nums2.size()) ? nums2[j + k/2 - 1] : INT_MAX;
        //二分法核心部分
        if(midVal1 < midVal2)
            return findKth(nums1, i + k/2, nums2, j, k - k/2);
        else
            return findKth(nums1, i, nums2, j + k/2, k - k/2);
    }

提交结果
题解2相当于每次缩小了一半的查找规模:
eg:1 2 3 4 5 6
7 8 9 10 11 12
第一次比较时,选定了k = 6, 7
以6为例
首先确定了 3 < 9 所以 3之前的数也都小于 9 (正序数组性质)
故只需要比较3之后和9之前的数 去找剩下的3个
时空复杂度为O(log(m+n))

思路不好想,但是想清楚之后,很容易写出递归式

题解三(直接思路稍作改进)

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        const int m = nums1.size();
        const int n = nums2.size();
        // 常量优化1
        if(!m && n){
            if(n%2)
                return nums2[n/2];
            else return (nums2[n/2]+nums2[(n-1)/2])/2.0;
        }
        if(!n && m){
            if(m%2)
                return nums1[m/2];
            else return (nums1[m/2]+nums1[(m-1)/2])/2.0;
        }
        // 中间数(做参考)
        int a = nums1[m/2];
        int b = nums2[n/2];
        // 常量优化2
        if(1==m && 1==n){
            return (a+b)/2.0;
        }
        // 左右op(插值)数
        int left = 0;
        int right = 0;
        
        // 先找到较长的数组,以此为基准放到新构建的数组里
        int t_s = n>=m ? n : m;
        // debug才发现的问题:vector可增长,所以如果初始化比较多会在copy后存在多余的0参与排序,所以需要先定准
        vector<int> newarray(t_s);
        // 这里可以重写一个Func
        if(m <= n){
            copy(nums2.begin(), nums2.end(), newarray.begin());
            // 从后往前插入
            for(int i=m-1; i >= 0; i--){
                if(nums1[i] <= b){
                    newarray.insert(newarray.begin()+n/2, nums1[i]);
                    left++;
                }else{
                    newarray.insert(newarray.begin()+n/2+1+left, nums1[i]);
                    right++;
                }
            }
            if(left >= right){
                //左边插的值多,所以中位数在前面
                sort(newarray.begin(), newarray.begin()+n/2+left);
            }else{
                //右边插的值多,所以中位数在后面
                sort(newarray.begin()+n/2+left+1, newarray.end());
            }
        }else{
            copy(nums1.begin(), nums1.end(), newarray.begin());
            for(int i=n-1; i >= 0; i--){
                if(nums2[i] <= a){
                    newarray.insert(newarray.begin()+m/2, nums2[i]);
                    left++;
                }
                else{
                    newarray.insert(newarray.begin()+m/2+1+left, nums2[i]);
                    right++;
                }
            }
            if(left >= right){
                sort(newarray.begin(), newarray.begin()+m/2+left);
            }else{
                sort(newarray.begin()+m/2+left+1, newarray.end());
            }
        }
        // 根据奇偶返回中位数
        if((n+m)%2) return (double)newarray[(n+m-1)/2];
        else return (newarray[(n+m)/2-1] + newarray[(n+m)/2])/2.0;
    }
};

在这里插入图片描述
这个结果着实不怎么样,果然是没啥改进的稍作改进!

题解四 划分数组(有用,记得看)

题解链接

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) {
            return findMedianSortedArrays(nums2, nums1);
        }
        
        int m = nums1.size();
        int n = nums2.size();
        int left = 0, right = m;
        // median1:前一部分的最大值
        // median2:后一部分的最小值
        int median1 = 0, median2 = 0;

        while (left <= right) {
            // 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
            // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
            int i = (left + right) / 2;
            int j = (m + n + 1) / 2 - i;

            // nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
            int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1]);
            int nums_i = (i == m ? INT_MAX : nums1[i]);
            int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1]);
            int nums_j = (j == n ? INT_MAX : nums2[j]);

            if (nums_im1 <= nums_j) {
                median1 = max(nums_im1, nums_jm1);
                median2 = min(nums_i, nums_j);
                left = i + 1;
            } else {
                right = i - 1;
            }
        }

        return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值