LeetCode 刷题记录——04寻找两个有序数组的中位数

LeetCode 刷题记录——04寻找两个有序数组的中位数

一 题目记录:

题目描述:

  • 给定两个大小为m和n的数组nums1和nums2。要求找出着两个有序数组的中位数,并且要求时间复杂度为O(log(m+n))。且nums1和nums2不会为空

  • 例:

    • Nums1 = [1,3]
      Nums2 = [2]
      中位数:2.0
      
    • Nums1 = [1,2]
      Nums2 = [3,4]
      中位数:(2+3)/2 = 2.5
      

思路:

  • 暴力解法:将两个数组合并为一个数组,然后在新的数组中找中位数,根据奇偶性来取中位数。时间复杂度为O(m+n),因为要遍历两个数组,找中位数的时候,直接根据下标计算。
  • 稍微好点的解法:和暴力解法的思路类似,对两个数组进行排序,但是不同的是,不需要将两个数组全部排序,从第一个元素开始,到(m+n)/2结束。时间开销比暴力解法好一点,但是仍然是O(m+n)量级
  • 题目中出现了log(m+n),习惯性地往二分法的方向去想一想
    • 二分实现:(看不懂的话请看后面的解疑部分)
    • 首先,我们需要用到两个“指针”(其实是下标),i 和 j ,分别指向nums1和nums2中的元素,且保证 i + j = (m+n+1)/2 (这里记,K= (m+n+1)/2 )[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XfPWoNA-1586251891845)(LeetCode 刷题记录——04寻找两个有序数组的中位数.assets/image-20200407161822674.png)]
      • 【疑问0】:m 和 n 有讲究吗?需要确保m<n吗?
      • 【疑问1】:为什么 要 i + j = (m+n+1)/2 ?
    • 其次,i 初始化为(m)/2,然后的对应的 j = K - i ; (把 i 的左边和 j 的左边元素统称为左部分元素 ,其右边称为右部分元素)
    • 然后,判断左部分元素是否都小于右部分元素,若是,就已经找到了中位数的位置;如不是,修改 i 和 j 的值,继续循环,直到满足条件为止
      • 【疑问2】如何修改 i 和 j 的值?
      • 【疑问3】满足条件指的是哪些?

代码:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        if(m>n){
            return findMedianSortedArrays(nums2,nums1);//保证m<n
        }
        
        int m_start,m_end;
        m_start = 0;
        m_end = m;         //较短数组的开始和结束部分

        while(m_start <= m_end){
            int i,j;
            i = (m_start+m_end + 1)/2;
            j = (m+n+1)/2 - i;
            //判断
            if(i!=0 && j!=n && nums1[i - 1] > nums2[j]){
                m_end = i-1;
            }
            else if(j!=0 && i!= m && nums2[j-1] > nums1[i]){
                m_start = i+1;
            }
            else{ //输出结果
                int LeftMax;
                //处理边界情况
                if(i == 0)
                    LeftMax = nums2[j-1];    //取nusm2的左半部分的最大值
                else if(j==0)
                    LeftMax = nums1[i-1];    //取nums1的左半部分的最大值
                else
                    LeftMax = max(nums1[i-1] , nums2[j-1]);  //如果不是处于边界上,就取左半部分最大值

                //分奇偶讨论
                if( (m+n)%2 ==1 )   //奇
                    return LeftMax;
                int RightMin = 0;
                if(i == m)
                    RightMin = nums2[j];    //取nusm2的右半部分的最小值
                else if(j == n)
                    RightMin = nums1[i];    //取nusm1的右半部分的最小值
                else
                    RightMin = min(nums1[i] , nums2[j]);//如果不是处于边界上,就取右半部分最小值

                return (LeftMax+RightMin)/2.0;  //偶数返回值
            }
        }
        return -1.0;
    }
};
  • 可能会遇到的问题:当时我也是石乐志,居然没有注意到 if 判断的先后顺序,如下:

  • //位于循环体中的判断部分
    //这是正确写法
    			if(i!=0 && j!=n && nums1[i - 1] > nums2[j]){
                    m_end = i-1;
                }
                else if(j!=0 && i!= m && nums2[j-1] > nums1[i]){
                    m_start = i+1;
                }
    //错误写法
    			if(nums1[i - 1] > nums2[j]&& i!=0 && j!=n){
                    m_end = i-1;
                }
                else if(nums2[j-1] > nums1[i] && j!=0 && i!= m){
                    m_start = i+1;
                }
    //错误写法中,会出现越界问题,因为此时有可能j==n 或者 i==m ,那么nums2[j]和nums1[i]就越界了
    

【解疑】

疑问0:

m 和 n 有讲究吗?需要确保m<n吗?

  • 有讲究,显然一个小的数组能加快运行速度,因此这里选择小的数组作为nums1
疑问1:

为什么 i + j = (m+n+1)/2 ?

  • 先看中位数的定义,即把一个数组分为左右两部分元素个数相等的部分,且左边元素的值 < 右边元素的值

  • 假设nums1和nums2有序合并的为Num,那么Num的长度为m+n,其中位数需要分奇偶讨论,如下:

    1. 偶数:中位数就是(m+n)/2
    2. 奇数:中位数就是(m+n+1)/2

    那么可以统一为:中位数(m+n+)/2,因为我们是用 Int 型,所以对于偶数 +1 或者不+1没有去区别

  • nums1和nums2是两个有序的数组,可以知道nums1的前 i 个元素和 nusm2的前 j 个元素都是比较小的元素,于是只要在 i + j = (m+n+1)/2 的前提下,再确保左部分元素 < = 右部分元素,那么就收工了

疑问2:

如何修改 i 和 j 的值?

  • 在回答如何修改以前,我们先来看看会有多少种情况产生,然后再依次对各种情况进行操作
  • 第一种:完美情况,左部分 <= 右部分
  • 第二种:左部分 > 右部分
    • ​ 对于这种情况,有两种可能导致,如下:
      1. nums1的 i-1 位置上的元素 > nums2的 j+1 位置上的元素image-20200407162750642
      2. nums2的 j-1 位置上的元素 > nums1的 i+1 位置上的元素image-20200407162846974
  • 这就是不满足的情况,对于左部分 > 右部分 的情况,显然是需要增大左部分元素的最大值,减小右部分元素的最小值,针对上述的两种不同情况,解决如下:
    1. 对于第一种情况,我们需要减小 i 的值,而增大 j 的值
    2. 对于第二种情况,我们需要增大 i 的值,而减小 j 的值
  • 增大多少,减小多少呢?
    • 其实只需要解决一个问题,增大多少,因为 i + j = K,
    • 回头看看 i 是如何确定的,我们一直在说二分法,到这里二分才出来,请准备好你的笔和纸,想不过来,就在纸上画出来。
    • 以num1为例,首先我们直接二分,即 i = m/2 , 如果此时不满足条件,需要增大 i ,那么就意味着需要取nums1的后部分来进行二分,然后得到一个新的 i .文字很苍白,上图(由红色部分到绿色部分)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-usTCFClq-1586251891846)(LeetCode 刷题记录——04寻找两个有序数组的中位数.assets/image-20200407170955269.png)]
疑问3:

满足条件指的是哪些?

  • 除了之前说的条件,还要注意一些边界问题,比如当 i 或者 j 到了数组头;以及i 或者 j 到了数组末尾时

  • 当 i 或者 j 到了数组头

    • 以 i 到数组头为例,左部分的最大值就是nums2的 j-1 位置上的元素,直接比较nums2[j-1]和右部分最小值
  • 当 i 或者 j 到了数组末尾
    些?

  • 除了之前说的条件,还要注意一些边界问题,比如当 i 或者 j 到了数组头;以及i 或者 j 到了数组末尾时

  • 当 i 或者 j 到了数组头

    • 以 i 到数组头为例,左部分的最大值就是nums2的 j-1 位置上的元素,直接比较nums2[j-1]和右部分最小值
  • 当 i 或者 j 到了数组末尾

    • 以 i 到了数组末尾为例,右部分的最小值就是nums2[j+1],直接比较这个值与左部分的最大值即可
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值