IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【算法-LeetCode】300. 最长递增子序列 -> 正文阅读

[数据结构与算法]【算法-LeetCode】300. 最长递增子序列

300. 最长递增子序列 - 力扣(LeetCode)

发布:2021年8月6日19:23:54

问题描述及示例

给你一个整数数组 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

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

提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104

我的题解

没记错的话,这是我碰到的第三道动态规划的题目了吧。但是说实话,一开始我并没有想到这道题可以用动态规划解决,反而是想到了递归或者回溯之类的。但是琢磨一番之后发现好像走不通,但是又实在没有思路了,所以就看了一眼LeetCode上【相关标签】提示。要是在面试中,这样估计是过不了了,因为思路都没能自己发现。

在这里插入图片描述
可以看到相关标签中出现了【动态规划】的身影,于是我立马就开始往动态规划上思考了。

此时我是忽视了【二分查找】这个标签,对不起,二分,我不是故意的( ̄ェ ̄;)。

于是,根据我之前做的总结(详情请参照:【算法-LeetCode】53. 最大子序和_赖念安的博客-CSDN博客),开始一步步尝试解题。

成功前的尝试(对dp数组的错误理解)

在下面的程序中,出现了动态规划题目中常见到的dp数组。根据总结,第一步就是确定这个dp数组的含义,也就是确定dp[i]表示的是什么。在我一开始的想法中,dp[i]应该是nums[i]之前(包括nums[i])的最长递增子序列的长度(事实上也确实应该这样设定)。但是写着写着,我就忘了初心(后来回想,似乎就是从dp[0]的初始化开始走歪的),没有紧扣dp[i]所表示的意义,开始将dp数组用来存储最长递增子序列本身,而非其长度。所以说,这动态规划的第一步我就出了问题。详解请看下方注释。

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  // 首先将dp数组初始化为空数组
  let dp = [];
  // 初始化dp数组,此乃万恶之源
  dp[0] = nums[0];
  // 由下标为1处开始遍历nums数组,遍历顺序为由前向后
  for (let i = 1, len = nums.length; i < len; i++) {
  	// 如果dp数组为空,或者当前遍历的数组元素比dp数组的末尾元素大,注意题目中说是
  	// 严格递增子序列,所以不要取等号则将当前遍历元素压入数组,且跳过本次循环的后续操作
    if (dp.length === 0 || nums[i] > dp[dp.length - 1]) {
      dp.push(nums[i]);
      continue;
    }
    // 如果dp数组不为空,或者当前遍历的数组元素不比dp数组的末尾元素大,则将dp数组的末尾元素弹出
    dp.pop();
    // 弹出之后,也要记得将当前遍历数组压入dp数组,但是压入之前要判断dp数组前是否已经包含该元素
    if (!dp.includes(nums[i])) {
      dp.push(nums[i]);
    }
  }
  // 遍历完成后,dp数组中就存储了nums数组中的最长严格递增子序列,返回其长度即可
  return dp.length;
};

我以为上面的程序已经可以通过测试用例了,提交之后发现,当nums = [0, 1, 0, 3, 2, 3] 时,所得的结果就是3,而非预期中的4,将程序放到Chrome开发者工具中debug,发现问题就出现在dp.pop();身上,因为当遍历到nums[2]时,由于nums[2] < dp[dp.length - 1](此时dp[0, 1])且dp.length !== 0,所以程序就会将dp[1]弹出,但是我们可以通过观察发现,其实这个元素1不应该被弹出。所以问题就卡在这儿了。

我的题解1(动态规划)

严格来说这并不是我的题解,因为关键的状态转移方程没有成功推导出来。

实在想不清楚该怎么改上面的程序了,所以只好推倒重来了。当我再次重复动态规划的解题步骤时,我突然就发现我没有明确dp数组的含义。这次我就坚守初心:dp[i]应该是nums[i]之前(包括nums[i])的最长递增子序列的长度,而最后返回的结果应该是dp数组中的最大值。但是又出现了新的问题:那状态转移方程是啥呢?这里我一开始只有一个模糊的概念,dp[i]应该是由dp[i-1]再拼接上nums[i]得到的,但是得实现判断nums[i]dp[i-1]里的元素的大小关系啊。

其实到这里我已经很接近答案了,但是很可惜的是,到最后我还是没有写出状态转移方程,于是我就开始去看了别人的题解,详情可参考【有关参考】,感谢【微信公众号:代码随想录】以及其他博主的分享与解析。

除了状态转移方程和我原先的思路不大一样外,博主的dp数组的初始化也不一样。详解请看下方注释。

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function(nums) {
  // 首先将dp初始化为一个长度与nums一样,而元素值均为1的数组,因为子序列长度起码有1
  let dp = new Array(nums.length).fill(1);
  // result用于存储dp数组中的最大值并最后作为返回值返回
  let result = 1;
  // 这句其实可以不写,但是为了动态规划题目的步骤完整性和格式化,所以加上
  dp[0] = 1;
  // 外层for循环用于由前向后遍历nums数组
  for(let i = 1, len = nums.length; i < len; i++) {
  	// 内层for循环用于确定nums[i-1]之前(包括nums[i-1])的最长递增子序列,
  	// 也就是找到dp[0] ~ dp[i-1]中的最大值
    for(let j = 0; j < i; j++) {
      // 如果当前遍历的外层for循环元素nums[i]比当前遍历的内层for循环元素nums[j]小,
      // 则直接不考虑将nums[i]纳入dp[i]的计算中,否则就取dp[j]+1和dp[i]中的较大值,
      // 当内层for循环结束后,就能保证dp[i]是保存的是最长递增子序列的长度
      if(nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
      }
    }
    // 内层for循环结束后,要更新result的值
    result = Math.max(result, dp[i]);
  }
  return result;
};


提交记录
54 / 54 个通过测试用例
状态:通过
执行用时:168 ms, 在所有 JavaScript 提交中击败了69.37%的用户
内存消耗:39.7 MB, 在所有 JavaScript 提交中击败了15.52%的用户
时间:2021/08/06 19:27

在上面的程序中,内层for循环也是关键的动态转移方程的一部分,一定要注意不能只看到Math.max(dp[i], dp[j] + 1)就认为是取dp[i]dp[j] + 1中的最大值,不能忽略这个语句是在内层for循环中,所以,经过至多i-1次的Maht.max()操作后,我们实际上是拿dp[x]0 ≤ x ≤i -1)中的最大值与dp[i]作比较。只不过上面的程序中将【取dp[x]0 ≤ x ≤i -1)中的最大值】和【dp[x]的最大值与dp[i]作比较】杂糅在一起写并同时加了一个nums[i] > nums[j]的判断。

在看官方题解的过程中,我还看到了一种利用【二分查找】的思想的解法,但是这里暂且按下不表,因为我还没怎么看懂[doge]。

在这里插入图片描述

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

更新:2021年8月6日19:29:12

参考:最长上升子序列 - 最长递增子序列 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年8月6日21:04:24
参考:【微信公众号:代码随想录 2021-03-09】动态规划:最长递增子序列

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 12:20:33  更:2021-08-07 12:21:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:25:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码