发布: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 数组用来存储最长递增子序列本身,而非其长度。所以说,这动态规划的第一步我就出了问题。详解请看下方注释。
var lengthOfLIS = function (nums) {
let dp = [];
dp[0] = nums[0];
for (let i = 1, len = nums.length; i < len; i++) {
if (dp.length === 0 || nums[i] > dp[dp.length - 1]) {
dp.push(nums[i]);
continue;
}
dp.pop();
if (!dp.includes(nums[i])) {
dp.push(nums[i]);
}
}
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 数组的初始化也不一样。详解请看下方注释。
var lengthOfLIS = function(nums) {
let dp = new Array(nums.length).fill(1);
let result = 1;
dp[0] = 1;
for(let i = 1, len = nums.length; i < len; i++) {
for(let j = 0; j < i; j++) {
if(nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
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】动态规划:最长递增子序列
|