LeetCode高频题4:求两个有序但不等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少

LeetCode高频题4:求两个有序但不等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述
本题的重要知识点:
【1】求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少


题目

LeetCode高频题4:求两个有序但不等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少?

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

算法的时间复杂度应该为 O(log (m+n)) 。
【而我今天的解法!全天下最优解:复杂度是o( min(log(n), log(m) ))

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


一、审题

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
整体融合是一个奇数长度的数组arr,好办,取第N/2小即可

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
整体融合是一个偶数长度的数组arr,好办,取第(N-1)/2 和 第N/2小求平均值即可,
即上中位数和下中位数的平均值,是真正的中位数


笔试AC解:

类似于:
【1】求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少
里面的双指针移动重新排序方法一样
直接用俩指针p1,p2将数组从新转移到help,等价于排序
复杂度o(n+m)
太复杂了
代码你可以看看

//我的思路,将arr扩充为arr1+arr2
    //L==0,mid==arr1.len,R=arr1.len+arr2.len - 1
    //然后玩归并排序中的merge
    //申请一个help数组,R-L+1长度
    //p1=L,p2=mid
    //arr的p1<=p2,则拷贝arr[p1++],否则拷贝arr[p2++]
    //当其中一个结束之后,剩下谁直接拷贝完事
    //然后将help放入arr中即可
    //注意这里可以不再新建arr,这样空间复杂度o(n+m)
    public static class Solution {
        public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            int p1 = 0;
            int p2 = 0;
            int i = 0;
            int[] help = new int[nums1.length + nums2.length];//复杂度出来了
            while (p1 < nums1.length && p2 < nums2.length){
                help[i++] = nums1[p1] <= nums2[p2] ? nums1[p1++] : nums2[p2++];
            }
            while (p1 < nums1.length){
                //nums1还没拷贝完
                help[i++] = nums1[p1++];
            }
            while (p2 < nums2.length){
                //nums2还没拷贝完
                help[i++] = nums2[p2++];
            }
            //上面只会进一个
            int n = help.length;
            double res = 0.0;
            if (n % 2 == 0) {
                res = help[(n >> 1) - 1] + help[n >> 1];//上下中位数的一半
                res /= 2;
            }
            else res = help[n >> 1];//奇数直接返回中位数即可

            return res;
        }
    }

    public static void test(){
        int[] arr = {1,2};
        int[] arr2 = {-1,3};
        Solution solution = new Solution();
        System.out.println(solution.findMedianSortedArrays(arr, arr2));
    }

    public static void main(String[] args) {
        test();
    }

测试

1.5

重点还是下面的最优解


f:求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少

本题的重要知识点:
你必须先学懂这个知识点,才能用来解决今天的题目
【1】求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少

有上面的函数f(arr1,s1,e1,arr2,s2,e2),f可以返回等长数组arr1和arr2的上中位数,比如总长N,则第N/2小那个数就是上中位数

    //求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少
    public static double f2(int[] arr1, int s1, int e1, int[] arr2, int s2, int e2) {
        //base case:当长度就是1时,直接对比,看谁小,返回谁
        if (s1 == e1) return Math.min(arr1[s1], arr2[s2]);

        //长度不止1时,下面二分查找
        int mid1 = (s1 + e1) >> 1;
        int mid2 = (s2 + e2) >> 1;

        int N = e1 - s1 + 1;//单个数组长
        boolean isEvenNum = (N & 1) == 0;//如果与上00001之后,末尾是0,必然是偶数

        if (isEvenNum){
            //偶数长度
            //不会就自己画个图看看
            //(1)如果arr1[mid1]=arr2[mid2],显然arr1[mid1]或者arr2[mid2]就是结果
            if (arr1[mid1] == arr2[mid2]) return arr1[mid1];
            else if (arr1[mid1] > arr2[mid2]){
                //(2)如果arr1[mid1]>arr2[mid2],则3 4不可能,1' 2'不可能是结果
                //下一次让e1=mid1-1,s2=mid2+1
                //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                e1 = mid1;//舍弃arr1的右边部分--e1不能越界
                s2 = mid2 + 1 > e2 ? e2 : mid2 + 1;//舍弃arr2的左边部分--e2不能越界
                return f2(arr1, s1, e1, arr2, s2, e2);
            }else {
                //(3)如果arr1[mid1]<arr2[mid2],跟(2)类似,则1 2不可能,3' 4'不可能是结果
                //下一次让e2=mid2-1,s1=mid1+1【反过来了哦】
                //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                e2 = mid2;//舍弃arr2的右边部分--e2不能越界
                s1 = mid1 + 1 > e1 ? e1 : mid1 + 1;//舍弃arr1的左边部分--e1不能越界
                return f2(arr1, s1, e1, arr2, s2, e2);
            }

        }else {
            //奇数长度——除了手动判断,其余和偶数一样
            //(1)如果arr1[mid1]=arr2[mid2],显然arr1[mid1]或者arr2[mid2]就是结果
            if (arr1[mid1] == arr2[mid2]) return arr1[mid1];
            else if (arr1[mid1] > arr2[mid2]){
                //(2)如果arr1[mid1]>arr2[mid2],则3 4 5不可能,1' 2'不可能是结果
                //**手动判断** 3'与2的关系,如果:
                //3'>2则必然3'就是结果
                if (arr2[mid2] > arr1[mid1 - 1]) return arr2[mid2];
                //否则1' 2' 3'不可能是结果
                else {
                    //下一次让e1=mid1-1,s2=mid2+1
                    //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                    e1 = mid1 - 1;//舍弃arr1的右边部分--e1不能越界
                    s2 = mid2 + 1 > e2 ? e2 : mid2 + 1;//舍弃arr2的左边部分--e2不能越界
                    return f2(arr1, s1, e1, arr2, s2, e2);
                }

            }else {
                //(3)如果arr1[mid1]<arr2[mid2],则1 2不可能,3' 4' 5'不可能是结果
                //**手动判断** 3与2'的关系,如果:
                //3>2'则必然3就是结果
                if (arr1[mid1] > arr2[mid2 - 1]) return arr1[mid1];
                //否则1 2 3不可能是结果
                else {
                    //下一次让e2=mid2-1,s1=mid1+1
                    //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                    e2 = mid2 - 1;//舍弃arr2的右边部分--e2不能越界
                    s1 = mid1 + 1 > e1 ? e1 : mid1 + 1;//舍弃arr1的左边部分--e1不能越界
                    return f2(arr1, s1, e1, arr2, s2, e2);
                }
            }
        }
    }

千万,千万要注意!!
代码中的偶数长度那,咱们不需要手动判断排除一个,调整e时,直接让e=mid
而奇数长度那,调整e时,由于手动判断了一个,排除掉这个,所以e真的要让mid-1

在这我耗费了太多时间调试代码!!!
不信你就看,发现f2进去时最终会不等长,一旦不等长,肯定就是你更新e除了问题!!!


咱们来看看不等长的俩数组,融合后求第k小的那个数的情况:

不妨设g(arr1,s1,e1,arr2,s2,e2,k),它干嘛的,
它是返回俩不等长数组,融合之后有序的数组arr中第k小那个数【排名第k那个】

比如;合并数组 arr= [1,2,3] ,中位数 2
整体融合是一个奇数长度的数组arr,好办,取第k=N/2小即可

实际上,咱们仍然是想要把不等长数组的arr1和arr2中的某些部分直接排除掉,排除掉a个我们为不可能是第k小的数。
目的呢,就是在数组arr1和arr2中去捞俩等长的数组C,D,灌入f,用f找抽出来的这俩等长数组CD融合之后有序数组的上中位数
如果CD总长M的话,咱们用f返回第b=M/2小那个数,就是CD的中位数
在这里插入图片描述
如果从arr1和arr2中舍弃掉的数量有a个,从f中返回的上中位数排名是b,恰好a+b=k,就是g要求的第k小的数
——这个思想你先可以了解,下面我举例说明为啥这么搞,你现在明白我要做这件事就行。
在这里插入图片描述
为啥要搞这个g函数呢???

有了g,本题就可以轻松解开:
不妨设:arr1和arr2长度为n和m,则融合arr的总长N=n+m,我们只需要看案例那的说明那样
(1)如果N是奇数长度,则用g函数,返回第k=N/2小那个数,就是答案,即arr1和 arr2融合后的中位数
(2)如果N是偶数长度,则用g函数,返回第k=(N-1)/2和k=N/2小那2个数,求平均就是答案,即arr1和 arr2融合后的中位数

这就是为啥我们有了f之后,还要整一个g的原因
这个f和g函数,是非常非常非常非常非常非常非常非常非常非常重要的重要重要的两个算法原型,可劲记住了!
这个f和g函数,是非常非常非常非常非常非常非常非常非常非常重要的重要重要的两个算法原型,可劲记住了!
这个f和g函数,是非常非常非常非常非常非常非常非常非常非常重要的重要重要的两个算法原型,可劲记住了!

我们现在来仔细说一下g函数该怎么求?
之前我定义的g,不妨设g(arr1,s1,e1,arr2,s2,e2,k),它干嘛的?
它是用来返回俩不等长数组A,B,融合之后有序的数组arr中第k小那个数
不妨设A是短数组,长度m,B是长数组,长度为n(m<n)

则g怎么求,具体看三种情况,做不同的处理,调用f,得到咱们要的第k小那个数
(1)当1<=k<=m时,最高不会超过短数组长度m
(2)当m<=k<=n时,最低不会到短数组长度m,最高不会超过长数组长度m
(3)当n<=k<=m+n时,最低不会到长数组长度m,最高不会超过短数组长度m+长数组长度n

来看具体例子:m=10,n=17时

(1)当1<=k<=m时,最高不会超过短数组长度m

显然k都没有超过m,我们要求第k小,岂不就是A中找k长,B中找k长,放入f,直接返回第2k/2=k小就得了,也就是f返回的等长数组融合后arr的上中位数吗?
因此,直接调用f就行

比如,k=7,就满足这个条件:1<=k<=10
其实,求第7小,咱们用不了太多,由于AB有序,咋着结果都不会是A中的8-10,最次也是7【这种情况是A全部在左边,B跟着排后面的极端情况】
咋着结果也不会是B中的8’–17’,最次也是7’【这种情况是B全部在左边,A跟着排后面的极端情况】
看下图:
在这里插入图片描述
因此呢,咱们直接舍弃掉8–10,8’–17’,捞出C和D是A和B中橘色那段
代码中抠清楚边界:C的范围是:A的0–k-1范围,D的范围是:B的0–k-1范围
所以调用f(A,0,k-1,B,0,k-1)
就是咱们的gf(A,0,m-1,B,0,n-1,k)整个AB的所有范围融合后的第k小那个数

(2)当m<=k<=n时,最低不会到短数组长度m,最高不会超过长数组长度m

比如:
k=15,就满足这个条件:10<=k<=17
我们应该看AB中哪些数绝对不可能是第15小呢???
极端情况就是A全部在左边,1–10比B都小,那最次B那开始数1’–4’,凑够14个,5’最次可能是结果
所以啊B中的1’–4’绝对不可能是结果,淘汰掉4个
在这里插入图片描述
还有一个极端情况就是B全部在左边,1’–15’比A都小,那最次B的15’可能是结果
所以啊B中的16’–17’绝对不可能是结果,淘汰掉2个【上图绿色哪些都淘汰了】

余下A中C是整体,余下B中D,我们想让CD是等长,调用f去求一个结果
在这里插入图片描述
但是你看,咱们淘汰拢共就4+2=6个,D从5’–15’一共11个,而C就是整体A,才10个,俩没法等长啊!
所以呢,咱们需要手动排除5’,看看5’与10的关系
如果5’>=10,则第k=15小一定是5’,很显然啊,A中10个被压着,B中左边4个被淘汰压着,一共去了14个,第15就是5’,没啥可说的
如果5’<10,则我们就可以判定5’一定不是答案,因为10左边有9个,5’左边4个,加5’自己也就14个数,还够不着15小呢
谁是答案呢?简单,就调用f,去搞C(A的整个范围)和D(B的6’–15’)
具体代码中的边界:
C:A的整个范围0–m-1
D:B的k-m位置–k-1位置【就是把A全排左边之后,k-m还剩几个位置呗,或者B全排左边,自然就是k-1那个位置】【就看极端情况的案例】
所以调用f(A,0,k-1,B,k-s,k-1)
就是咱们的g(A,0,m-1,B,0,n-1,k)整个AB的所有范围融合后的第k小那个数【一定要细细品味整个下标,不是乱搞得,而是极端情况下算出来的】

注意手动排除的5’位置是k-m-1位置,就是极端排除A之后,从B开始数的最次可能结果那个位置

(3)当n<=k<=m+n时,最低不会到长数组长度m,最高不会超过短数组长度m+长数组长度n

比如:
k=23,就满足这个条件:17<=k<=27
我们应该看AB中哪些数绝对不可能是第23小呢???

极端情况就是A全部在左边,1–10比B都小,那最次B那开始数1’–12’,凑够22个,13’最次可能是结果
所以啊B中的1’–12’绝对不可能是结果,淘汰掉12个
待会调用f时,D是B中的至少是k-m-1位置–n-1位置【左边边界可能不一定是k-m-1,可能还需要+1】
在这里插入图片描述
还有一个极端情况就是B全部在左边,1’–17’比A都小,那最次A还要排除1–5,这样总共排除22个,最次A的6可能是结果
所以啊A中的1–5绝对不可能是结果,淘汰掉5个【上图绿色哪些都淘汰了】
待会调用f时,C是A中的至少是k-n-1位置–m-1位置【左边边界可能不一定是k-m-1,可能还需要+1】

余下A中C是整体,余下B中D,我们想让CD是等长,调用f去求一个结果
在这里插入图片描述
但是你看,咱们淘汰拢共就12+5=17个,假设你现在直接调用f去求C和D,后面是5+5=10,你从f中返回第5小的数
这样的话是17+5=22,第22小,压根不是咱的第23小哇!!!

所以呢,咱们要手动判断6和17’的关系,13’和10的关系
在这里插入图片描述
如果6>=17’,6一定是结果,因为6左边压了5个,B中被压17个,自然排除22个,恰好6就是第23小
上面这几个条件还不满足的话,还要看下面这个条件
13’>=10,13’一定是结果,因为13’左边压12个,A被压10个,共22个,恰好13’就是第23小
上面这几个条件还不满足的话,那就直接排除6和13’了呗
相当于在原来排除的17个基础上,再排除俩,一共淘汰了19个数
余下C是A的7–10,D是B的14’–17’,共8个数,用f求CD第4小上中位数
19+4=23
这个数才是真的第23小,这就是为啥我们要手动排除6和13’的原因

明白?
咱们来看CD的范围:
C是A的k-n–m-1【极端排除B所有n个,再手动判断6那个位置,故需要+1】
D是B的k-m–n-1【极端排除A所有m个,再手动判断13’那个位置,故需要+1】

注意手动排除的位置6是k-n-1位置,13’是k-m-1位置

由于g中咱们实际上只用了长度mn,还有k来求范围,故g就不要整s1–e1和s2–e2了
直接用g(A,B,k)即可

手撕g(A,B,k)的代码,返回AB融合之后第k小那个数

关键就像上面分析那样,扣清楚下标的边界:
一个是手动判断位置的下标
另一个是调用f求等长数组CD的下标范围

    //复习:手撕g(A,B,k)的代码,返回AB融合之后第k小那个数
    //关键就像上面分析那样,扣清楚下标的边界:
    //一个是手动判断位置的下标
    //另一个是调用f求等长数组CD的下标范围
    public static double g(int[] A, int[] B, int k){
        //把长数组给longArr,短数组给shortArr
        int[] longArr = A.length > B.length ? A : B;
        int[] shortArr = A.length <= B.length ? A : B;
        int m = shortArr.length;//短数组长
        int n = longArr.length;//长数组长

        //则g怎么求,具体看三种情况,做不同的处理,调用f,得到咱们要的第k小那个数
        //下面都是处理短数组和长数组,默认A短数组,默认B长数组,看文章的边界推导
        //(1)当1<=k<=m时,最高不会超过短数组长度m
        if (1<= k && k <= m){
            //显然k都没有超过m,我们要求第k小,岂不就是A中找k长,B中找k长,
            // 放入f,直接返回第2k/2=k小就得了,也就是f返回的等长数组融合后arr的上中位数吗?
            return f2(shortArr, 0, k - 1, longArr, 0, k - 1);
        }else if (m< k && k <= n){
            //(2)当m<=k<=n时,最低不会到短数组长度m,最高不会超过长数组长度m
            //本代码结合我文章的图看,
            // 注意手动排除的5'位置是k-m-1位置,就是极端排除A之后,从B开始数的最次可能结果那个位置
            if (longArr[k - m - 1] >= shortArr[m - 1]) return longArr[k - m - 1];
            else return f2(shortArr, 0, m - 1, longArr, k - m, k - 1);
            //C:A的整个范围0--m-1
            //D:B的k-m位置--k-1位置【就是把A全排左边之后,k-s还剩几个位置呗,或者B全排左边,自然就是k-1那个位置】【就看极端情况的案例】
            //所以调用**f(A,0,k-1,B,k-m,k-1)**
        }else if (n< k && k <= m + n){
            //(3)当n<=k<=m+n时,最低不会到长数组长度m,最高不会超过短数组长度m+长数组长度n
            //注意手动排除的位置6是k-n-1位置,13'是k-m-1位置
            if (shortArr[k - n - 1] >= longArr[n - 1]) return shortArr[k - n - 1];
            if (longArr[k - m - 1] >= shortArr[m - 1]) return longArr[k - m - 1];
            //C是A的k-n--m-1【极端排除B所有n个,再手动判断6那个位置,故需要+1】
            //D是B的k-m--n-1【极端排除A所有m个,再手动判断13'那个位置,故需要+1】
            return f2(shortArr, k - n, m - 1, longArr, k - m, n - 1);
        }

        //一定会有满足其一的条件
        return -1;
    }

测试一把:

    //测试看看
    public static void test2(){
        int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
        int[] arr2 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};
//        int[] arr1 = {1,2,3,4,5};
//        int[] arr2 = {1,2,3,4,5,6};
        int k = 7;
        int k2 =15;
        int k3 =23;
        int ans = g(arr1, arr2, k);
        int ans2 = g(arr1, arr2, k2);
        int ans3 = g(arr1, arr2, k3);
        System.out.println(ans);
        System.out.println(ans2);
        System.out.println(ans3);
    }



    public static void main(String[] args) {
//        test();
        test2();
    }

结果很ok

4
8
13

面试最优解:求两个有序但不等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少

有了g,本题就可以轻松解开:
不妨设:arr1和arr2长度为n和m,则融合arr的总长N=n+m,我们只需要看案例那的说明那样
(1)如果N是奇数长度,则用g函数,返回第k=N/2小那个数,就是答案,即arr1和 arr2融合后的中位数
在这里插入图片描述

(2)如果N是偶数长度,则用g函数,返回第k=(N-1)/2和k=N/2小那2个数,求平均就是答案,即arr1和 arr2融合后的中位数
在这里插入图片描述

    //有了g,本题就可以轻松解开:
    // 求两个有序但不等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少
    //不妨设:arr1和arr2长度为n和m,则融合arr的总长N=n+m,我们只需要看案例那的说明那样
    //(1)如果N是**奇数长度**,则用g函数,返回第k=N/2小那个数,就是答案,
    //(2)如果N是**偶数长度**,则用g函数,返回第k=(N-1)/2和k=N/2小那2个数,**求平均**就是答案
    public static double medianOfTwoArr(int[] arr1, int[] arr2){
        if ((arr1 == null || arr1.length == 0) &&
                (arr2 == null || arr2.length == 0)) return 0;
        if (arr1 == null || arr1.length == 0) {
            //看arr2长度是奇数偶数
            int N = arr2.length;
            if ((N & 1) == 1){
                //奇数长度,直接去N/2那个数
                return arr2[N >> 1];
            }else {
                double a = arr2[(N - 1) >> 1];
                double b = arr2[N >> 1];
                return (a + b) / 2;//取上下中位数的平均值
            }
        }
        if (arr2 == null || arr1.length == 0) {
            //看arr1长度是奇数偶数
            int N = arr1.length;
            if ((N & 1) == 1){
                //奇数长度,直接去N/2那个数
                return arr1[N >> 1];
            }else {
                double a = arr1[(N - 1) >> 1];
                double b = arr1[N >> 1];
                return (a + b) / 2;//取上下中位数的平均值
            }
        }
        //上面是过滤掉arr1或者arr2有null的情况

        //下面就是正常arr1和arr2不null的情况
        int N = arr1.length + arr2.length;//融合长度N
        //看融合的arr长度是奇数偶数
        if ((N & 1) == 1){
            //奇数长度,直接用g求(N+1)/2那个数
            return g(arr1, arr2, (N >> 1) + 1);
        }else {
            double a = g(arr1, arr2, ((N - 1) >> 1) + 1);//加1是因为我逻辑上就是不要0的
            double b = g(arr1, arr2, (N >> 1) + 1);
            return (a + b) / 2;//取上下中位数的平均值
        }
    }

这里调用g,为啥要加1呢?因为我们在逻辑上就没用0,所以第k小就是第几名,需要整体+1
测试:

    //测试
    public static void test3(){
//        int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
//        int[] arr2 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};
//        int[] arr1 = null;
//        int[] arr2 = {1,2,3,4,5,6,7};
//        int[] arr2 = {1,2,3,4,5,6};

//        int[] arr1 = {1,3};
//        int[] arr2 = {2};
        int[] arr1 = {1,2};
        int[] arr2 = {3,4};

        double ans = medianOfTwoArr(arr1, arr2);
        System.out.println(ans);
    }
2.5

换案例:

int[] arr1 = {1,3};
int[] arr2 = {2};
2.0

再换测试案例

int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
int[] arr2 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};

总共27个,第27/2=13小就是咱要的位置
A的7左边压6个,B的7左边压6个,12个,第13就是A的7或者B的7,问题不大吧!!!

7.0

然后提交给LeetCode测试

LeetCode是num表示数组,改一个名字就行:

class Solution {
    //leetcode
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if ((nums1 == null || nums1.length == 0) &&
                (nums2 == null || nums2.length == 0)) return 0;
        if (nums1 == null || nums1.length == 0) {
            //看arr2长度是奇数偶数
            int N = nums2.length;
            if ((N & 1) == 1){
                //奇数长度,直接去N/2那个数
                return nums2[N >> 1];
            }else {
                double a = nums2[(N - 1) >> 1];
                double b = nums2[N >> 1];
                return (a + b) / 2;//取上下中位数的平均值
            }
        }
        if (nums2 == null || nums2.length == 0) {
            //看arr1长度是奇数偶数
            int N = nums1.length;
            if ((N & 1) == 1){
                //奇数长度,直接去N/2那个数
                return nums1[N >> 1];
            }else {
                double a = nums1[(N - 1) >> 1];
                double b = nums1[N >> 1];
                return (a + b) / 2;//取上下中位数的平均值
            }
        }
        //上面是过滤掉arr1或者arr2有null的情况

        //下面就是正常arr1和arr2不null的情况
        int N = nums1.length + nums2.length;//融合长度N
        //看融合的arr长度是奇数偶数
        if ((N & 1) == 1){
            //奇数长度,直接用g求(N+1)/2那个数
            return g(nums1, nums2, (N >> 1) + 1);
        }else {
            double a = g(nums1, nums2, ((N - 1) >> 1) + 1);//加1是因为我逻辑上就是不要0的
            double b = g(nums1, nums2, (N >> 1) + 1);
            return (a + b) / 2;//取上下中位数的平均值
        }
    }

    //求两个有序等长数组arr1,arr2,合成有序数组arr之后的上中位数是多少
    public static double f2(int[] arr1, int s1, int e1, int[] arr2, int s2, int e2) {
        //base case:当长度就是1时,直接对比,看谁小,返回谁
        if (s1 == e1) return Math.min(arr1[s1], arr2[s2]);

        //长度不止1时,下面二分查找
        int mid1 = (s1 + e1) >> 1;
        int mid2 = (s2 + e2) >> 1;

        int N = e1 - s1 + 1;//单个数组长
        boolean isEvenNum = (N & 1) == 0;//如果与上00001之后,末尾是0,必然是偶数

        if (isEvenNum){
            //偶数长度
            //不会就自己画个图看看
            //(1)如果arr1[mid1]=arr2[mid2],显然arr1[mid1]或者arr2[mid2]就是结果
            if (arr1[mid1] == arr2[mid2]) return arr1[mid1];
            else if (arr1[mid1] > arr2[mid2]){
                //(2)如果arr1[mid1]>arr2[mid2],则3 4不可能,1' 2'不可能是结果
                //下一次让e1=mid1-1,s2=mid2+1
                //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                e1 = mid1;//舍弃arr1的右边部分--e1不能越界
                s2 = mid2 + 1 > e2 ? e2 : mid2 + 1;//舍弃arr2的左边部分--e2不能越界
                return f2(arr1, s1, e1, arr2, s2, e2);
            }else {
                //(3)如果arr1[mid1]<arr2[mid2],跟(2)类似,则1 2不可能,3' 4'不可能是结果
                //下一次让e2=mid2-1,s1=mid1+1【反过来了哦】
                //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                e2 = mid2;//舍弃arr2的右边部分--e2不能越界
                s1 = mid1 + 1 > e1 ? e1 : mid1 + 1;//舍弃arr1的左边部分--e1不能越界
                return f2(arr1, s1, e1, arr2, s2, e2);
            }

        }else {
            //奇数长度——除了手动判断,其余和偶数一样
            //(1)如果arr1[mid1]=arr2[mid2],显然arr1[mid1]或者arr2[mid2]就是结果
            if (arr1[mid1] == arr2[mid2]) return arr1[mid1];
            else if (arr1[mid1] > arr2[mid2]){
                //(2)如果arr1[mid1]>arr2[mid2],则3 4 5不可能,1' 2'不可能是结果
                //**手动判断** 3'与2的关系,如果:
                //3'>2则必然3'就是结果
                if (arr2[mid2] > arr1[mid1 - 1]) return arr2[mid2];
                //否则1' 2' 3'不可能是结果
                else {
                    //下一次让e1=mid1-1,s2=mid2+1
                    //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                    e1 = mid1 - 1;//舍弃arr1的右边部分--e1不能越界
                    s2 = mid2 + 1 > e2 ? e2 : mid2 + 1;//舍弃arr2的左边部分--e2不能越界
                    return f2(arr1, s1, e1, arr2, s2, e2);
                }

            }else {
                //(3)如果arr1[mid1]<arr2[mid2],则1 2不可能,3' 4' 5'不可能是结果
                //**手动判断** 3与2'的关系,如果:
                //3>2'则必然3就是结果
                if (arr1[mid1] > arr2[mid2 - 1]) return arr1[mid1];
                //否则1 2 3不可能是结果
                else {
                    //下一次让e2=mid2-1,s1=mid1+1
                    //去递归求f(arr1,s1,e1,arr2,s2,e2)的结果就是咱们要的结果
                    e2 = mid2 - 1;//舍弃arr2的右边部分--e2不能越界
                    s1 = mid1 + 1 > e1 ? e1 : mid1 + 1;//舍弃arr1的左边部分--e1不能越界
                    return f2(arr1, s1, e1, arr2, s2, e2);
                }
            }
        }
    }

    //复习:手撕g(A,B,k)的代码,返回AB融合之后第k小那个数
    //关键就像上面分析那样,扣清楚下标的边界:
    //一个是手动判断位置的下标
    //另一个是调用f求等长数组CD的下标范围
    public static double g(int[] A, int[] B, int k){
        //把长数组给longArr,短数组给shortArr
        int[] longArr = A.length > B.length ? A : B;
        int[] shortArr = A.length <= B.length ? A : B;
        int m = shortArr.length;//短数组长
        int n = longArr.length;//长数组长

        //则g怎么求,具体看三种情况,做不同的处理,调用f,得到咱们要的第k小那个数
        //下面都是处理短数组和长数组,默认A短数组,默认B长数组,看文章的边界推导
        //(1)当1<=k<=m时,最高不会超过短数组长度m
        if (1<= k && k <= m){
            //显然k都没有超过m,我们要求第k小,岂不就是A中找k长,B中找k长,
            // 放入f,直接返回第2k/2=k小就得了,也就是f返回的等长数组融合后arr的上中位数吗?
            return f2(shortArr, 0, k - 1, longArr, 0, k - 1);
        }else if (m< k && k <= n){
            //(2)当m<=k<=n时,最低不会到短数组长度m,最高不会超过长数组长度m
            //本代码结合我文章的图看,
            // 注意手动排除的5'位置是k-m-1位置,就是极端排除A之后,从B开始数的最次可能结果那个位置
            if (longArr[k - m - 1] >= shortArr[m - 1]) return longArr[k - m - 1];
            else return f2(shortArr, 0, m - 1, longArr, k - m, k - 1);
            //C:A的整个范围0--m-1
            //D:B的k-m位置--k-1位置【就是把A全排左边之后,k-s还剩几个位置呗,或者B全排左边,自然就是k-1那个位置】【就看极端情况的案例】
            //所以调用**f(A,0,k-1,B,k-m,k-1)**
        }else if (n< k && k <= m + n){
            //(3)当n<=k<=m+n时,最低不会到长数组长度m,最高不会超过短数组长度m+长数组长度n
            //注意手动排除的位置6是k-n-1位置,13'是k-m-1位置
            if (shortArr[k - n - 1] >= longArr[n - 1]) return shortArr[k - n - 1];
            if (longArr[k - m - 1] >= shortArr[m - 1]) return longArr[k - m - 1];
            //C是A的k-n--m-1【极端排除B所有n个,再手动判断6那个位置,故需要+1】
            //D是B的k-m--n-1【极端排除A所有m个,再手动判断13'那个位置,故需要+1】
            return f2(shortArr, k - n, m - 1, longArr, k - m, n - 1);
        }

        //一定会有满足其一的条件
        return -1;
    }
}

在这里插入图片描述
你就说牛逼不牛逼,100%超过所有人,这是全天下最优解!!!

最优解的时间复杂度:o(min(log(n),log(m)))

情况是这样的,g在调用f时,
每一次,三个条件只进其一,而且进去调用f是等长的,这个长度永远不会超过短数组的长m

每次递归进g,数据规模缩小一半,故复杂度log(m)

即最优解的时间复杂度:o(min(log(n),log(m)))


总结

提示:重要经验:

1)本题最重要的两个函数,第一个f,等长数组arr1和arr2从s1–e1,s2–e2求融合数组上中位数,分为奇数长度偶数长度的两种大情况
2)第二个g,从AB不等长数组中找第k小那个数,根据k与m和n长度的关系,决定淘汰哪些不可能的结果,最后捞等长数组CD调用f求得结果。
3)根据AB融合长度的奇偶性,决定咱们要求N/2小,还是(N-1)/2与N/2小的平均值,作为AB融合后的中位数–细细品味
4)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值