Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >算法细节系列(7):354. Russian Doll Envelopes

算法细节系列(7):354. Russian Doll Envelopes

作者头像
用户1147447
发布于 2019-05-26 11:46:11
发布于 2019-05-26 11:46:11
63200
代码可运行
举报
文章被收录于专栏:机器学习入门机器学习入门
运行总次数:0
代码可运行

354. Russian Doll Envelopes

在做这道题之前,我们先来看一道它的简化版本300. Longest Increasing Subsequence,官网给出了两种解法,时间复杂度分别为O(n2)O(n^2)和O(nlogn)O(n \log n).

Longest Increasing Subsequence

Problems:

Given an unsorted array of integers, find the length of longest increasing subsequence. For example, Given [10, 9, 2, 5, 3, 7, 101, 18], The longest increasing subsequence is [2, 3, 7, 101], therefore the length is 4. Note that there may be more than one LIS combination, it is only necessary for you to return the length. Your algorithm should run in O(n2) complexity. Follow up: Could you improve it to O(n log n) time complexity?

求最长递增子序列,一道dp题,简单判断下,如果给出数组[10,9,2,5,3],那么它的最长序列长度为3,在这种基础情况下增加数组元素[10,9,2,5,3,7,101,18],我们进一步得到最长序列长度为4。如果我们用dp表示的话,dp[i] = Math.max(dp[i],dp[j]+1),代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public int lengthOfLIS(int[] nums) {

        if (nums.length == 0) return 0;

        int[] dp = new int[nums.length+1];
        int max = 0;
        for (int i = 0; i < nums.length; i++){
            dp[i] = 1;
            for (int j = 0; j < i; j ++){
                if (nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max;
    }

注意if条件的判断,只有当满足数组元素递增情况下,我才更新dp,很容易理解。max只需要随着dp[i]更新就好了。

官网还提出提示了另外一种更高效的算法,它的时间复杂度为O(nlogn)O(n \log n),那么它应该怎么做?其实,在上述做法当中,你能看到一些瑕疵,它的第一层循环是定位当前i元素,并与前i-1个元素比较,这有点像插入排序,而我们知道插入排序在定位到第i个元素时,前面的元素已经是有序的了,有一种做法可以加快我们的查找速度,二分!!!而像第一种解法,虽然做了同样的O(n2)O(n^2)次的比较,但它没有更新元素的顺序,所以它所隐含的有序状态并没有得到高效利用。

所以自然而然的想法就出来了,我们每当定位到当前元素时,就把当前元素放到它该去的位置,如[10,9]比较后,显然应该变成[9,10],但这种状态由题目意思是非法的,所以砍掉[10],就有了[9],而此时定位到2,又更新为[2],继续i定位到5时,变成了[2,5],这种状态是合法的,所以有了最新最长递增序列,它的好处就很明显了,本身有序,所以当前元素i在搜索位置插入时,可以优化到logn\log n,接下来就重点关注如何二分了!

该问题就变成了,只有【后续的元素大于当前最后一个元素】时,就增加数组长度并插入到最后位置。其它的,当小于等于最后一个元素时快速定位到指定位置即可。我们只要抓住括号内这个关键点即可。

抽象一下,我们只需要找到最大的下标i,使得dp[i] < key即可,所以有如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int lengthOfLIS(int[] nums) {

        if(nums.length == 0) return 0;

        int[] dp = new int[nums.length];
        int len = 1;
        dp[0] = nums[0];
        for (int i = 1; i < nums.length; i++){
            int index = binarySearch(dp, 0, len-1, nums[i]);
            dp[index + 1] = nums[i];
            if (index + 1 == len){
                len ++;
            }
        }

        return len;

    }

    //典型的一种二叉树应用,保证它的准确性
    private int binarySearch(int[] nums, int lf, int rt, int key){

        while (lf < rt){
            int mid = lf + (rt + 1 - lf) / 2;

            if(nums[mid] >= key){
                rt = mid - 1;
            }else{
                lf = mid;
            }
        }

        if (nums[lf] < key) return lf; 

        return -1;
    }

可以去调试它去理解它的过程,这里总结两点:

  • 当前i元素,插入到[0,len-2]的位置上,你可以丢弃它不看。
  • 当前i元素,插入到 len - 1 的位置上时,它是你当前最长递增子序列中的一个解。
  • 当前i元素,插入到 len 的位置上时,长度被更新,可见 len - 1 位置上元素的重要性。

升级版

Problems:

You have a number of envelopes with widths and heights given as a pair of integers (w, h). One envelope can fit into another if and only if both the width and height of one envelope is greater than the width and height of the other envelope. What is the maximum number of envelopes can you Russian doll? (put one inside other)

Example:

Given envelopes = [[5,4],[6,4],[6,7],[2,3]], the maximum number of envelopes you can Russian doll is 3 ([2,3] => [5,4] => [6,7]).

如果理解了上面那道简化版,那么这道题自然不在话下,但仍需要注意一些细节。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes == null || envelopes.length == 0 || envelopes[0] == null || envelopes[0].length != 2)
            return 0;
        Arrays.sort(envelopes, new Comparator<int[]>() {
            public int compare(int[] arr1, int[] arr2) {
                if (arr1[0] == arr2[0])
                    return arr2[1] - arr1[1];
                else
                    return arr1[0] - arr2[0];
            }
        });
        int dp[] = new int[envelopes.length];
        int len = 0;
        for (int[] envelope : envelopes) {
            int index = Arrays.binarySearch(dp, 0, len, envelope[1]);
            if (index < 0)
                index = -(index + 1);
            dp[index] = envelope[1];
            if (index == len)
                len++;
        }
        return len;
    }

此处,我们需要知道官方lib库中提供的binarySearch返回的index,如果能够找到对应的key,那么直接返回对应的下标,而当找不到当前元素时,它返回的是最大的index,使得nums[index] < key,返回-(index)-1,所以对应的 +1 取反即为我们要插入的地方。

还需要注意一个地方,即comparator的比较顺序,先比较宽度,把宽度小的放在前面,如果宽度相等,则把长度长的放在前面,这是关键!!!理由很简单,当宽度相同时,我们有理由取最大的长度,如果取最小,那么对后续的序列,会出现宽度相同,而长度大的也被放入到了dp中,这种情况并不符合。不过说实话,想到这并不容易。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年04月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
文心一言 VS 讯飞星火 VS chatgpt (209)-- 算法导论15.4 6题
要设计一个 O(nlgn) 时间的算法来求一个 n 个数的序列的最长单调递增子序列,我们可以使用动态规划结合二分查找的方法,也就是经典的“最长递增子序列”(Longest Increasing Subsequence, LIS)问题。
福大大架构师每日一题
2024/03/18
1200
文心一言 VS 讯飞星火 VS chatgpt (209)-- 算法导论15.4 6题
文心一言 VS 讯飞星火 VS chatgpt (208)-- 算法导论15.4 5题
在 Go 语言中设计一个 O(n^2) 时间复杂度的算法来求一个 n 个数的序列的最长单调递增子序列(Longest Increasing Subsequence, LIS)可以使用动态规划的方法。以下是一个实现示例:
福大大架构师每日一题
2024/03/06
1880
文心一言 VS 讯飞星火 VS chatgpt (208)-- 算法导论15.4 5题
LeetCode 题目解答—— 第 311 到 371 题
[Updated on 9/22/2017] 如今回头看来,里面很多做法都不是最佳的,有的从复杂度上根本就不是最优解,有的写的太啰嗦,有的则用了一些过于 tricky 的方法。我没有为了这个再更新,就让它们去吧。
四火
2022/07/19
9130
LeetCode 题目解答—— 第 311 到 371 题
链表问题
A stringSof lowercase letters is given. We want to partition this string into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing the size of these parts.
大学里的混子
2019/03/01
4960
算法题目
import java.util.*; class TreeNode { int val; TreeNode left; TreeNode right; public TreeNode(int value){ this.val = value; } } class ListNode { int val; ListNode next; public ListNode(int value){ this.val = value; } } class TrieNode {
大学里的混子
2019/03/08
7590
二分查找总结
二分查找是在每次匹配后,将查找的空间一分为二的算法,二分查找应该是有序的数组进行查找.
Tim在路上
2020/08/04
4940
文心一言 VS 讯飞星火 VS chatgpt (205)-- 算法导论15.4 1题
在Go语言中,求两个序列的最长公共子序列(Longest Common Subsequence, LCS)可以使用动态规划(Dynamic Programming, DP)的方法。下面是一个Go语言实现的示例代码,用于找到给定两个序列的LCS:
福大大架构师每日一题
2024/03/06
2000
文心一言 VS 讯飞星火 VS chatgpt (205)-- 算法导论15.4 1题
LeetCode 300. Longest Increasing Subsequence (Tag:Binary Search)
Given an unsorted array of integers, find the length of longest increasing subsequence.
用户7447819
2021/07/23
2830
674. Longest Continuous Increasing Subsequence
LWC 49:674. Longest Continuous Increasing Subsequence Problem: Given an unsorted array of integers, find the length of longest continuous increasing subsequence. Example 1: Input: [1,3,5,4,7] Output: 3 Explanation: The longest continuou
用户1147447
2019/05/26
3900
【DP】300. Longest Increasing Subsequence
Given an unsorted array of integers, find the length of longest increasing subsequence.
echobingo
2019/06/14
5100
LintCode 最长上升子序列题目分析
给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。 说明最长上升子序列的定义: 最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。https://en.wikipedia.org/wiki/Longest_increasing_subsequence
desperate633
2018/08/22
2440
LintCode 最长上升子序列题目分析
LeetCode 300. 最长递增子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
freesan44
2021/11/14
8310
LeetCode 300. 最长递增子序列
300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
名字是乱打的
2021/12/24
5650
LWC 49:673. Number of Longest Increasing Subsequence
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014688145/article/details/77923593
用户1147447
2019/05/26
3790
Leetcode 300. Longest Increasing Subsequence
**解析:**Version 1,最长递增子序列,典型的动态规划问题,定义状态:以nums[i]作为结尾元素的最长递增子序列的长度,状态转移方程:遍历nums[i]之前的元素nums[j],如果nums[i] > nums[j],则其最长递增子序列的长度为max(dp[i], dp[j] + 1),遍历之后,可以找到以nums[i]作为结尾元素的最长递增子序列长度,最终返回的是所有元素的最长递增子序列长度中最长的一个。Version 2是一种技巧,使用order作为有序序列保持最长递增子序列长度,当新元素比有序序列的最后一个元素大时,此时增加新元素到有序序列中,否则,则将新元素插入到当前序列中,替换比其大或相等的元素,保证左侧元素都比它小,此时长度不变,order中始终保留较小的元素,这样利于插入新元素。order的长度等于最长递增子序列长度,但order的数据不一定等于最长递增子序列的数据。
Tyan
2021/07/29
3860
Leetcode 300. Longest Increasing Subsequence
LeetCode Weekly Contest 26解题思路
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014688145/article/details/68953387
用户1147447
2019/05/26
3530
<dp>求最长的公共子串
Given an unsorted array of integers, find the length of longest increasing subsequence.
大学里的混子
2018/11/18
1.1K0
LintCode 最长上升子序列题目分析代码
说明 最长上升子序列的定义: 最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。https://en.wikipedia.org/wiki/Longest_increasing_subsequence
desperate633
2018/08/22
2630
leetcode 300. Longest Increasing Subsequence
找到整数数组中最长的递增子数组。该子数组可以为不连续的。如题目中例子所示,[10, 9, 2, 5, 3, 7, 101, 18]得到的最长子数组为[2,3,7,101]。
眯眯眼的猫头鹰
2018/10/31
4370
【狂热算法篇】解锁数据潜能:探秘前沿 LIS 算法
嘿,各位编程爱好者们!今天带来的 LIS 算法简直太赞啦 无论你是刚入门的小白,还是经验丰富的大神,都能从这里找到算法的奇妙之处哦!这里不仅有清晰易懂的 C++ 代码实现,还有超详细的算法讲解,让你轻松掌握 LIS 算法的三种解法:暴力、动态规划、贪心加二分查找,彻底搞懂它在数据处理、资源分配和文本处理中的强大应用呢 看完保证让你收获满满,记得点赞,收藏三连,关注我,后续还有更多精彩的算法干货分享哦 让我们一起开启算法的奇妙之旅,提升编程技能,征服代码世界吧!
羑悻的小杀马特.
2025/01/23
2030
【狂热算法篇】解锁数据潜能:探秘前沿 LIS 算法
相关推荐
文心一言 VS 讯飞星火 VS chatgpt (209)-- 算法导论15.4 6题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验