力扣刷题第3天——寻找两个正序数组的中位数

一、题目介绍

来源:力扣(LeetCode)
链接:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

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

算法的时间复杂度应该为 O(log (m+n)) 。

示例1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

提示:

  ·nums1.length == m
  ·nums2.length == n
  ·0 <= m <= 1000
  ·0 <= n <= 1000
  ·1 <= m + n <= 2000
  ·-106 <= nums1[i], nums2[i] <= 106

二、题解

2.1二分查找

2.1.1思路

由于两数组长度和有奇数和偶数两种情况,因此所求中位数有直接返回第(m+n)/2小的数据和返回[((m+n)/2)+((m+n)/2+1)]/2两种情况,则该问题可简化为寻找第k小的元素,k=(m+n)/2)或k=(m+n)/2+1)。

由于该题目控制了时间复杂度为O(log (m+n)),因此无法使用合并数组查找的方式求解(该方法时间复杂度为O(m+n)),因此我们使用伪动态数组的方法求解。

伪动态数组的大概思想是通过判断num1[k/2-1]和num2[k/2-1]大小来进一步缩短数组并修改待查数的位置k,进而减少查找次数,降低时间复杂度。

 在伪动态数组的处理中可能会出现一些特殊情况,及某个数组读取完毕但仍未找到满足的数据(继续读取数组越界),某个数组为空数组,以及k=1的情况。

①越界问题:根据越界前最后一次排除工作更新k的值,而不能像常规那样直接变为k/2。

②空数组:最好解决的情况->直接到另一个非空数组查找所需的数据。

③k=1:对比排除后的新数组的第一位,返回较小的值(k=1意味着寻找两个动态数组中最小的值)。

操作实例:

越界情况示例:

2.1.2代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int sum=nums1.size()+nums2.size();
        if(sum%2==1){//针对不同总长要返回不同处理的中位数
            return getKthElement(nums1,nums2,(sum+1)/2);
        }
        else{
            return (getKthElement(nums1,nums2,sum/2)+getKthElement(nums1,nums2,sum/2+1))/2.0;
        }
    }

    int getKthElement(vector<int>& nums1, vector<int>& nums2,int k){
        int m=nums1.size();
        int n=nums2.size();
        int index1=0,index2=0;//伪动态数组的头指针

        while(true){
            if(index1==m){//num1数组已经越界,所以中位数一定在num2数组中
                return nums2[index2+k-1];//找第k个数=找动态下标为k-1的数
            }
            if(index2==n){
                return nums1[index1+k-1];
            }
            if(k==1){//k=1判断
                return min(nums1[index1],nums2[index2]);
            }

            //一般情况
            int newIndex1=min(index1+k/2-1,m-1);//防止越界
            int newIndex2=min(index2+k/2-1,n-1);
            int pivot1=nums1[newIndex1];//待比较的中值->下标为k/2-1的数据
            int pivot2=nums2[newIndex2];
            if(pivot1<=pivot2){
                k-=newIndex1-index1+1;
                //更新k,直接减去排查掉的数据量,减少对越界情况的k处理的一步
                index1=newIndex1+1;//伪动态数组的头指针
            }
            else{
                k-=newIndex2-index2+1;
                index2=newIndex2+1;
            }
        }
    }
};

2.1.3代码分析

还得靠数学好(bushi)

通过伪动态数组的处理大大减少了数据判断的次数,将时间复杂度降低为O(log(m+n))。

2.2划分数组

2.2.1思路

核心思想:二分查找

将nums1和nums2近似对半分开,使nums1形成nums1[0]~nums1[i-1]和nums1[i]~nums1[m-1](nums1的长度为m),同理将nums2分成nums2[0]~nums2[j-1]和nums2[j]~nums2[n-1]两部分,则根据正序数组的设定可以得到nums1[i-1]<=nums1[i],nums2[j-1]<=nums2[j]。

因此可以将左右两部分分别合并,即使nums1[0]~nums1[i-1]和nums2[0]~nums2[j-1]成为新的左半部分,nums1[i]~nums1[m-1]和nums2[j]~nums2[n-1]成为新的右半部分,则会有max(nums1[i-1],nums2[j-1])<=min(nums1[i],nums2[j])的结论。

官方图形解释:

对数组nums1简化称为数组A:

           left_A            |          right_A
    A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]

对数组nums2简化称为数组B:

           left_B            |          right_B
    B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

合并可获得:

          left_part          |         right_part
    A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
    B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]

那么针对两数组总长度为奇数的情况有结论:

①左半部分总长=右半部分总长+1

②左半部分最大值<=右半部分最小值

③所求中位数mid=左半部分的最大值

针对两数组总长度为偶数的情况有结论:

①左半部分总长=右半部分总长

②左半部分最大值<=右半部分最小值

③所求中位数mid=(左半部分的最大值+右半部分的最小值)/2

则根据以上这六点可以发现,无论总长奇偶,条件②都是相同的,而条件①和③则可以通过一定形式转为统一:

 则整个题目可以简化为不断枚举符合的i值记录对应的j值(使用左右两个双指针,不断像中央逼近,每次判断后砍掉一半不可能满足条件的数据,从而缩小时间复杂度),直到获得左半部分的最大值和右半部分的最小值(直到左右指针反向移动,即左指针跑到右指针的右面,则可以确定查找到两半部分的中介位置)后返回答案。

2.2.2代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size()>nums2.size()){//使整个程序保证形参nums1.size()<=nums2.size()
            return findMedianSortedArrays(nums2,nums1);
        }

        int m=nums1.size();
        int n=nums2.size();
        int median1=0,median2=0;//median1是左侧最大值,median2是右侧最小值
        int left=0,right=m;//用来寻找符合题意的左侧最大值和右侧最小值

        while(left<=right){
            int i=(left+right)/2;//用于确定nums1[i-1]位置->要直接取中位,那样减一后才是符合题意的nums1左半部分最大位置
            int j=(m+n+1)/2-i;//用于确定nusm2[j]的位置

            int nums_im1=(i==0?INT_MIN:nums1[i-1]);//nums[i-1]越界(小于0),返回计算机无限小
            int nums_i=(i==m?INT_MAX:nums1[i]);//nums[i]越界(大于m),返回计算机无限大
            int nums_jm1=(j==0?INT_MIN:nums2[j-1]);//nums[j-1]
            int nums_j=(j==n?INT_MAX:nums2[j]);//nums[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;//判断返回不同情况下的不同结果
    }
};

2.2.3代码分析

每次循环使需要判断是数量减少一半,同时因为可以调换实参传递顺序,使长度更短的数组作为形参中的nums1进行判断,因此时间复杂度为O(log min(m,n)),空间复杂度为O(1)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于力扣刷题C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷题中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷题中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷题中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷题中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷题中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷题中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷题中,使用 string 可以方便地处理字符串相关的问题。 9. 注意边界条件:在力扣刷题中,边界条件往往是解决问题的关键。需要仔细分析题目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷题中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值