在复杂度o(n)的要求下找到n个数的中位数(n为奇数)_LeetCode 04寻找两个正序数组的中位数(困难)二分法...

博客详细介绍了如何在O(log(m+n))的时间复杂度下,利用二分法解决LeetCode 04寻找两个正序数组中位数的问题。通过分析和示例,解释了二分法在解决此问题中的应用,以及奇数和偶数情况下中位数的确定方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ccc157977e82a15185c1d046ac430b75.gif

LeetCode 04寻找两个正序数组的中位数(困难)二分法

60f1a2c5a91df2ab1fc095f9ef885081.png

呕心沥血的一个题解,点赞关注在看,一键三联,一起加入我们打卡!。

5a7ca2f37fbf57ac8df5e53518d48a46.png

题目描述:

呕心沥血的一个题解,点赞关注收藏,一键三联,一起加入我们打卡!

题目描述:

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

归并法(O(m+n))

分析之前小吐槽一句,这题自己真的没想到O(log(m+n))的方法,只能想到O(m+n)的归并,没想到怎么去使用二分,后来看了题解也是才明白。但也算自己理解了和大家分享一下。

对于这个问题或许本身不难,但是可能难在O(log(m+n))的时间复杂度上。

如果真的无法想到好的方法,先想着过关,该用什么方法呢?

法一暴力法:
可以将两个数组添加到一个总的数组中,然后给这个数组进行排序。正常的排序是O(nlogn)的时间复杂度。排序之后根据奇数偶数取中位数即可。

法二归并法:
给的两个数组本身是有序的,想必熟悉归并排序的朋友们应该能一下就想出来这个方法,两个有序的.只需按照以下流程即可完成归并:

  • 待归并的两个数组分别设置两个指针leftindex,rightindex均从0开始。新数组也设置游标index。

  • 比较两数组leftindex和rightindex位置的值,较小的那个赋值到新数组中同时新数组游标和较小的那个游标均加一。

    5536bb734e2f0ca04c004c56525aee90.png
    在这里插入图片描述
  • 重复到其中一个数组遍历完,另一个数组剩余值直接加入后面即可。

    38b9b5ca5fc4ef053202e577ad0cfbc8.png
    在这里插入图片描述

实现代码:

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
         int a[]=new int[nums1.length+nums2.length];
         int lindex=0,rindex=0;
         int index=0;
         while (lindex            if(nums1[lindex]            {
                a[index++]=nums1[lindex++];
            }
            else {
                a[index++]=nums2[rindex++];
            }
        }
         while (lindex                a[index++]=nums1[lindex++];
        }
         while (rindex                a[index++]=nums2[rindex++];
        }
         if(a.length%2==0)
             return (double)(a[a.length/2-1]+a[a.length/2])/2;
         else {
            return a[a.length/2];
        }

     }

二分法(O(log(m+n)))

想到有序的,又是O(log(m+n))的时间复杂度,估计大部分人都能想到二分,我当时也是一样,但是该怎么想呢这就是一个问题。记录下我当初错误的想法:

二分,二分找到两个中间的。然后正常有个长的,有个短的,根据两个数值比较分类推测中位数应该在哪个区间……然后大脑就断电了。

对于中位数的简单分析

  • 如果两个数组长度和为奇数,那么最终这个中位数是由一位数确定的。

  • 如果两个数组长度和为偶数,那么最终这个中位数是由两位数取平均值确定的。

对两个数组的简单分析

  • 两个数组应该有一个长一点,另一个点一点(等长也不影响)。

  • 中位数可能让两个数组都分成两部分:一部分小于中位数,一部分大于中位数。但两个部分合起来总数量应该一致

    2425495dbf8a6a27d4f8321c95555630.png
    在这里插入图片描述

对两数组和中位数位置分析

  • 我们知道两数组虽然可能等长(不影响),但正常情况应该是一个长(m)一个短(n)。长短数组分别对应的坐标m1和n1和中位数坐标有什么关系?

  • 无论总和奇数偶数,都满足(m1+n1)=(m+n)/2;因为两个数组都是有序的所以总共小于中位数的占一半。其中m和n是定值。也就是不管你怎么变动,这两个坐标编号是总和为定值得

如何分析为定值得坐标

  • 既然两个坐标的总和为定值,那么可不可以把其中一个当为自变量,一个看成自变量呢?比如x+y=5你不好分析但是y=5-x,你分析x同时y就确定了。对吧?

  • 那么选择长的那个作为变量还是短的那个作为变量呢?短的,为啥,主要因为如果从长的当成变量咱们有些区域无法对应到短的(因为长度即使加上短的所有也到不了一半,处理起来麻烦)

    59ded181f6745f7156ce864a15671459.png
    在这里插入图片描述
  • 但是短的就可以很好避免这种情况:

    cec25b5b32cebc8d5b6bf19d8b327526.png
    在这里插入图片描述

所以我们就用二分去查找小的这个区间,找到最终的结果,你可能会问:什么样情况能够满足确定这条线的附近就是产生中位数的

  • 二分进行查找编号的时候,满足左侧都比线右侧小才行。这种情况在二分查找就是一个平衡的结果。

    235929720c5838566706bde7a690b3fa.png
    在这里插入图片描述

最后找到这个index线了。取值比较你还要有注意的地方

  • 取左侧的时候左侧如果有index为0,取右侧的时候index为最大值:

    a47b55b73331c5ae9941bc1082f8ac22.png
    在这里插入图片描述
  • 所以这种在你最后取值的时候,需要考虑左右侧是否有值。同时取长的那个也要比较,因为可能出现等长情况例如:1 2 3 4,和5 6 7 8这种去到临界。需要判断当然在实现过程用三目运算简化!

总的来说:

  • 根据短的进行二分查找位置,先找到线index,说明中位数在附近产生。(奇数偶数在查找因为要除2可以通用表达式)

  • 如果总个数奇数,那么就是线左侧最大的那个(两个比较或只有一个)

  • 如果总个数偶数,那么就是线左侧最大的那个(两个比较或只有一个)和线右侧最小的那个(两个比较或只有一个)的值取平均,注意是double类型

  • 其他注意点,搞清index从0开始,搞清逻辑上的第几个和数组显示使用的第几个的index的区别

附上代码:

public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
    if(nums1.length>nums2.length)//保证num1长度小,如果不小我交换一下
    {
        int team[]=nums2.clone();
        nums2=nums1;
        nums1=team;
    }   
     int k=(nums1.length+nums2.length+1)/2;//理论中位数满足的位置
     int left=0,right=nums1.length;//二分查找短的

     while (left//找到对应位置int m1=(left+right)/2;//在短的位置int m2=k-m1;//在长的第几个//System.out.println(m1+" "+m2);if(nums1[m1]1])//left右移
            left=m1+1;else {//right左移
            right=m1;
        }
    }//System.out.println(left+" "+k);//左侧最大和右侧最小那个先算出来再说,根据奇偶再使用double leftbig= Math.max(left==0?Integer.MIN_VALUE:nums1[left-1], k-left==0?Integer.MIN_VALUE:nums2[k-left-1]);double rightsmall=Math.min(left==nums1.length?Integer.MAX_VALUE:nums1[left],k-left==nums2.length?Integer.MAX_VALUE:nums2[k-left]);//System.out.println(rightsmall);if((nums1.length+nums2.length)%2==0)
    {return (leftbig+rightsmall)/2;
    }else {return leftbig;
    }       
 }

结语

本次打卡结束,再接再励。

至于其他方法暂时先不学了,感觉这题还是挺有难度的,需要搞明白要点时候。

最近往期精彩回顾

json从立地到成佛

一种O(n)的排序——计数排序引发的围观风波

两分钟真能搞懂桶排序

学弟不懂原码反码补码,气的我给女朋友讲了一夜

LeetCode 01两数之和&02两数相加

LeetCode 03无重复字符的最长子串(滑动窗口)

听说长得俊的更喜欢给人点赞在看关注,关注公众号:bigsai,回复加群,咱们一起打卡,目前已经一百多位csdn朋友加入打卡。

ab14462e1daa6544ba64d59b23ced1fd.png

长按识别

关注我们

4650fd039041ddec71ceda1f9176217a.png转载是一种动力 分享是一种美德

58c972027147ae770d05d7ee6a1fe9e5.png

56efd3e4a93963eb1f29f705a2542910.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值