[路飞]_leetcode-300-最长递增子序列

简介: leetcode-300-最长递增子序列

网络异常,图片无法展示
|


「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战


[题目地址][B站地址]


给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。


子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。


示例 1:


输入: nums = [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长递增子序列是 [2,3,7,101],因此长度为 4 。
复制代码


示例 2:


输入: nums = [0,1,0,3,2,3]
输出: 4
复制代码


示例 3:


输入: nums = [7,7,7,7,7,7,7]
输出: 1
复制代码


提示:


  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104


进阶:


  • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?


本题是是求最长递增子序列,那么涉及到这种求最优解的题,一般首先想到的解题方法就是动态规划


说到动态规划,就要说一下两个概念


状态定义


所谓状态定义,就是用一个函数符号 f(x) 以及该函数符号对应的含义描述,该函数符号对应的值就是要求解的值


如果没有接触过动态规划的小伙伴,看完上面这句话,可能你还是不太明白状态定义到底是什么,接下来我们就用本题举例,来看下本题如何做状态定义


首先我们思考一个问题:本题最长递增子序列的值和什么有关系?


答:本题最长子序列的值只和当前下标位置又关


我们想一下,如果我们当前在下标 0 的位置,最长递增子序列的长度就是 1,以此类推,随着下标位置的变化,最长递增子序列的长度也会发生变化


所以本题我们定义 dp[i] 表示下标 i 位置的最长递增子序列的长度


接下来我们看一下什么是状态转移方程


状态转移方程


状态定义完成后,我们并不能解决本题,我们知道 dp[i] 代表 下标 i 位置的最长递增子序列的长度,可是 0 下标位置的值很简单就是 1,但是 dp[nums.length-1] 的值是多少怎么得到呢?


这就是状态转移方程要做的事


我们想一下最长递增子序列满足的条件是什么?


是后面的元素要比前面的元素大,这就要求两个条件,假设后面的元素下标为 i,前面的元素下标为 j,则需要满足 j < i && nums[j] < nums[i],由此,我们就可以推断出本题的状态转移方程


dp[i] = dp[j]+1 //其中 j < i && nums[j] < nums[i]


所以我们只需要在 i 之前找到所有满足条件的 j 位置,让 dp[i] 等于所有满足条件的 dp[j] 中的最大值 +1即可


以上就是动态规划解题的两个步骤,也就形成了本题题解1的解题思路


题解1


var lengthOfLIS = function(nums) {
    const len = nums.length,
    dp = Array(len).fill(1);
    for(let i = 1;i<len;i++){
        for(let j = 0;j<i;j++){
            if(nums[j]<nums[i]) dp[i] = Math.max(dp[i],dp[j]+1)
        }
    }
    return Math.max(...dp)
};
复制代码


提交通过,用时 190ms 左右,在用时方面,只击败了 50% 左右的用户,时间复杂度是 O(n²)


题解2


上面题解的效率,作为一个有追求的前端,我肯定是不满意的,所以我用了一个下午的碎片化时间来思考有没有更优的解法,最终在第二天上午我放弃了

网络异常,图片无法展示
|


现阶段水平有限,没有想到更优的解法,动态规划也是刚开始学,于是我只能不甘心的去看一看大佬的题解 参考题解


这里说一下,通常我不会先看讲解的过程,而是直接拿到代码,运行没有问题,然后自己去理解代码


我感觉这样的话,有一个自己理解的过程,印象会更深一些,而且因为是自己理解的东西,过一段时间再回头看,也能很容易的回想起来


当然,如果理解的过程中遇到问题或者有了自己的理解,还是会去看一下讲解,前者解决问题,后者对比下自己的理解是否正确


所以接下来我的讲解过程会和原题解不太一样,感兴趣的小伙伴可以去看下原题解,顺便帮大佬点个赞,留个言,吃水不忘挖井人嘛!

网络异常,图片无法展示
|


我们来看一下示例1中给出的 nums = [10,9,2,5,3,7,101,18]


我们在该数组中想找到一个最长的递增子序列,如果把元素值的大小转换为高度,以上数组则如下图


网络异常,图片无法展示
|


在示例2给出的 nums = [0,1,0,3,2,3]如下图:


网络异常,图片无法展示
|


那我们想要求得的最长递增子序列,就是找到一个最长的连续上升的阶梯,那从这样的图中找出最长递增子序列,就是要把那些违反该性质的柱子,也就是元素,干掉。


那这个过程怎么做呢?


我们可以遍历所有的柱子,当前面的柱子大于它的时候,它就替换前面的柱子,如果前面没有大于它的柱子,就放在前面柱子的后面,而为了保证我们找到的上升阶梯最长,所以我们优先替换左侧的柱子


示例1的过程如下:


10
9
2
2 5
2 3
2 3 7
2 3 7 101
2 3 7 101 18
复制代码


示例2的过程如下:


0
0 1
0 1
0 1 3
0 1 2
0 1 2 3
复制代码


最后,柱子的个数,就是最长递增子序列的长度


那这个过程为什么可以合理的求得最长上升子序列呢?


因为我们遍历元素的过程是从左向右的,也就是从下标 0 到数组末尾,所以在一次柱子从低到高排列的过程中,下标肯定是有序的


又因为我们把高的柱子放在矮的柱子后面,所以柱子的高低,也就是元素值的大小也是有序的


可能最后我们最终的柱子不是对应原数组的一组阶梯柱子,那是因为后续更新的柱子没有完整覆盖上一组柱子


我知道这里如果用动画演示会更好一些,但因为现阶段水平有限,所以没有搞动图,但是我们可以用元素值来表示整个过程


注意:因为是拿后面柱子覆盖前面柱子,所以这里的示例的组合过程是由下向上的


以示例1为例


2 3 7 18  此时为一次完成阶梯
2 3 7 101 此时为一次完成阶梯
2 5
9
10
复制代码


以示例2为例


0 1 2 3  此时为一次完成阶梯
0 1 2    此时为一次完成阶梯
0 1 3    此时为一次完成阶梯
0 1
0 1
复制代码


以数组 [10,9,2,5,3,101,18,7,1,2] 为例


1 2 7    在更新下一次阶梯
1 3 7    在更新下一次阶梯
2 3 7    此时为一次完成阶梯
2 3 18   此时为一次完成阶梯
2 3 101  此时为一次完成阶梯
2 3
2 5
2
9
10
复制代码


以数组 [10,9,2,5,3,18,7,1,2,6,101] 为例


1 2 6 101 此时为一次完成阶梯
1 2 6   在更新下一次阶梯
1 2 7   在更新下一次阶梯
1 3 7   在更新下一次阶梯
2 3 7   此时为一次完成阶梯
2 3 18  此时为一次完成阶梯
2 3
2 5
2
9
10
复制代码


后续动画搞出来我再来更新


代码如下:


var lengthOfLIS = function(nums) {
    const pillars = [nums[0]];
    let num = 1;
    for(let i = 1;i<nums.length;i++){
        let cur = nums[i],l = 0,r = num-1;
        // 如果比现有最高的柱子还要高,放在末尾
        if(cur>pillars[r]){
            pillars[num] = cur;
            num++;
        }else{
            // 二分查找合适的柱子
            while(l<r){
                const mid = (l+r) >> 1;
                if(pillars[mid]<cur) l = mid+1
                else if(pillars[mid]>cur) r = mid
                else l = r = mid;
            }
            pillars[l] = cur;
        }
    }
    // 返回柱子数量
    return num;
};
复制代码


提交后用时 70ms 左右,击败了 90+% 的用户,时间复杂度 是 O(nlogn)


最后祝大家周末愉快!但是也要记得学习哦!


网络异常,图片无法展示
|


如有任何问题或建议,欢迎留言讨论!

相关文章
|
7月前
|
Python
【Leetcode刷题Python】376. 摆动序列
文章提供了解决LeetCode "摆动序列" 问题的Python实现代码,通过遍历整数数组并使用两个变量 down 和 up 来记录正差和负差摆动序列的长度,最终返回最长摆动子序列的长度。
57 0
|
7月前
|
Python
【Leetcode刷题Python】946. 验证栈序列
LeetCode题目“946. 验证栈序列”的Python解决方案,通过模拟栈的压入和弹出操作来验证给定的两个序列是否能通过合法的栈操作得到。
43 6
|
7月前
|
算法 Python
【Leetcode刷题Python】剑指 Offer 33. 二叉搜索树的后序遍历序列
本文提供了一种Python算法,用以判断给定整数数组是否为某二叉搜索树的后序遍历结果,通过识别根节点并递归验证左右子树的值是否满足二叉搜索树的性质。
34 3
|
7月前
|
Python
【Leetcode刷题Python】105. 从前序与中序遍历序列构造二叉树
LeetCode上105号问题"从前序与中序遍历序列构造二叉树"的Python实现,通过递归方法根据前序和中序遍历序列重建二叉树。
43 3
|
7月前
|
算法 Python
【Leetcode刷题Python】300. 最长递增子序列
LeetCode 300题 "最长递增子序列" 的两种Python解决方案:一种使用动态规划,另一种使用贪心算法结合二分查找。
58 1
|
7月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
60 0
|
7月前
|
Python
【Leetcode刷题Python】674. 最长连续递增序列
LeetCode 674题 "最长连续递增序列" 的Python解决方案,使用动态规划算法找出给定整数数组中最长连续递增子序列的长度。
124 0
|
9月前
|
存储 算法 数据可视化
哈希表法快速求解最长连续序列 | 力扣128题详细解析
哈希表法快速求解最长连续序列 | 力扣128题详细解析
|
9月前
|
存储 算法
力扣经典150题第四十六题:最长连续序列
力扣经典150题第四十六题:最长连续序列
38 0
|
6月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行