吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1861|回复: 1
收起左侧

[Java 转载] LeetCode_Subject4

[复制链接]
lpc0828 发表于 2021-4-13 10:57
题目:给定两个大小分别为mn的正序(从小到大)数组nums1nums2。请你找出并返回这两个正序数组的中位数
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?时隔好久好久,因为工作原因
耽搁了好久,也给了自己很多的思考时间(时间复杂度,空间复杂度)
看了知乎上面一个比较透彻的讲解之后 ,感觉到一丝丝灵感 但是还没完全掌握 希望可以在接下来的题目里面 运用
时间复杂度:算法的渐进时间复杂度
大O符号表示法:
1.常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
2.对数阶O(logN)
在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。
我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
3.线性阶O(n)
for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度
4.线性对数阶O(nlogN)
将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
5.平方阶O(n2)
如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n2) 了。
6.立方阶O(n3)
O(n3)相当于三层n循环
7.K次方阶O(n^k)
O(n^k)相当于K层n循环
8.指数阶(2^n)
知乎没说 大概就非常非常大 非常非常低
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。
空间复杂度 O(n)
1.空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
2.空间复杂度 O(n)
不管代码执行多少次 占用的临时空间都为n的情况

接下来研究这道题目了
[Java] 纯文本查看 复制代码
    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        List<Integer> integers = new ArrayList<>();
        for (int i = 0; i < nums1.length; i++) {
            integers.add(nums1[i]);
        }
        for (int i = 0; i < nums2.length; i++) {
            integers.add(nums2[i]);
        }
        Collections.sort(integers);
        // 这是双数
        if (integers.size() % 2 == 0) {
            Integer integer = new ArrayList<Integer>(integers).get(integers.size() / 2 - 1);
            Integer integer1 = new ArrayList<Integer>(integers).get(integers.size() / 2);
            return (integer + integer1) / 2.0;
        } else {
            return new ArrayList<Integer>(integers).get(integers.size() / 2).doubleValue();
        }
    }


基本操作最笨办法先来一个
把他们两个数组 放到一个list里面 然后按照大小排序
然后判断size size为偶数 取size/2+size/2-1 的下标  然后求平均值
如果是奇数,直接找size/2的下标 就是它了

这种办法属于最笨最笨的办法 完全不符合题目中的进阶要求
我去学习了一下官方的代码
[Java] 纯文本查看 复制代码
 public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            // 把长度较长的数组放到后面
            if (nums1.length > nums2.length) {
                return findMedianSortedArrays(nums2, nums1);
            }
            // 第一个的长度
            int m = nums1.length;
            // 第二个的长度
            int n = nums2.length;
            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 ? Integer.MIN_VALUE : nums1[i - 1]);
                int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]);
                int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
                int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]);
                // 第一个数组的左边 要小于第二个数组的右边
                if (nums_im1 <= nums_j) {
                    median1 = Math.max(nums_im1, nums_jm1);
                    median2 = Math.min(nums_i, nums_j);
                    // 左边向右
                    left = i + 1;
                } else {
                    // 右边向左
                    right = i - 1;
                }
            }
            return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
        }
    }

数组1[1,3,5,7] 长度m
数组2[2,4,6,8,10] 长度n
我用画图的方式 一步一步执行 拆分学习
它选择把长度稍大一点的集合 放在第二个的位置,为了避免数组越界  
主要的思路是这样 在int计算中3/2=1  2/2=1
所以我们可以规避一个问题。奇数和偶数 都可以用(m+n-1)来表示那个最中间的元素
要在脑海里把两个数组分为四个部分 第一个数组的左边+第二个数组的左边 =我们最终排列出来的最中间的那个位置 左边的全部数组
第一个数组右边+第二个数组右边=我们最终排列出来的最中间的那个位置 右边的全部数组
把握住我们的船舵 就可以开始远航
第一次开始循环的时候  i和j 的位置 是指第一个数组最中间 和第二个数组最中间
我们只要找到这两个合适的位置 就找到了我们所需要的东西
然后 我们刚开始找 的位置肯定是不合适的  怎么判断什么时候合适
i左边的数字一定会小于它右边的数字 要让它也小于第二个数组j右边的数字, 这才是合适的位置
如果它小于右边 那我们也不能直接断定 就是合适的位置 应该再把i往右移动,找到那个最合适的位置 它再往右走一步 就会大于j右边 那我们就找到了
当left>right的时候 我们循环完了
这个时候 在我们已经找到那个点的时候 就应该结束了 但是代码不知道我们是不是真的找到了 所以它还会一直找
每次找到合适的时候 就会记录下 i,j左边最大的 和右边最大的  
它找到最合适的之后 继续移动ij 就会开始动right 不会再去动最大值最小值
这就是一个比较精妙的点

这是一个评论里面的  也比较巧妙 之前看不懂  写完帖子之后回头看 有点灵感
[Java] 纯文本查看 复制代码
    public static double 评论(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        // 总长度
        int length = nums1.length + nums2.length;
        int val1 = 0, val2 = 0, p = 0, k = 0;
        // p i  k j 直接从0开始一个一个往右读
        while (p + k < length / 2 + 1) {
            val1 = val2;
            // 防止下标越界 第二个数组被读完了
            if (k == n) {
                val2 = nums1[p++];
                continue;
            }
            // p == m 也是防止下标越界 第一个数组被读完了 记录的是第二个数组
            if (p == m || nums1[p] >= nums2[k]) {
                val2 = nums2[k++];
            } else {
                val2 = nums1[p++];
            }
        }
        if (length % 2 == 0) {
            return (double) (val1 + val2) / 2;
        }
return  val2;    }

原理大概是这样
两个数组从左到右依次循环  还是秉着上面的原则 找最中间
每次都往前走一步 ,一直取最大值,p+k到最中间的时候 如果总长度奇数val2就是我们要的数
如果是总长度偶数 (val1+val2)/2就是我们要的结果
想象一下 把两个数组 放到一个数组里面 我们知道我们要的数 是最中间(m+n+1)/2
我们循环数组 一个一个循环 循环到最中间 我们直接就拿到值了

比较精妙的两种方法 还是官方的更diao一点

下一题学习88题 和这道题有一些相似

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

落破的书生 发表于 2021-4-13 12:25
膜拜大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 18:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表