- 300.最长递增子序列">Algorithm:300.最长递增子序列
- Review
- Tips
- Share
完成日期:2021.07.18
Algorithm:300.最长递增子序列
这道题是为了巩固动态规划所做的一道题,也属于动态规划的经典题目。
根据题目的要求,是求出一个序列里最长的递增子序列的长度。其实这道题是进行了简化的,没有让求最长的递增子序列。
分析下题目能够知道,这道题用递归算是最自然、也最容易想到的:
- 以原始序列中的每个元素作为子序列的第1个元素
- 那么只得到该元素后面所有元素的各种组合得到的最长长度,再+1就可以了
- 那么此时问题就变成了剩余的元素中,将所有元素再作为第1个元素,然后再递归的求出其后面所有元素所构成的序列组合中的最长长度,再+1就可以了
- 从而构成了递归
用文字写出来还是太抽象,直接看下递归的代码吧:
def L(nums, i):'''nums: 原始数组i : 当前需要计算的值的索引值这里要注意的是递归的值必须是索引i,因为数组里的值是可以重复的,所以不能传递数组值'''if i == len(nums)-1: #到数组末尾了return 1max_len = 1for j in range(i+1, len(nums)):if nums[j] > nums[i]:max_len = max(max_len, L(nums, j)+1)return max_lendef lengthOfLIS(nums):max_len = 1for i in range(len(nums)):max_len = max(max_len, L(nums, i))return max_len
代码仔细看的话,还是很简单的,但有一个问题:提交上去后,会报超时。其实要根据70题的经验也知道,这里肯定会超时,因为里面涉及到大量的重复计算。
知道原因了,那么改进也很简单,带上备忘录就好了:
class Solution:memo = {}def L(self, nums, i):if i in self.memo:return self.memo[i]if i == len(nums)-1:return 1length = 1for j in range(i, len(nums)):if nums[j] > nums[i]:length = max(length, self.L(nums, j)+1)self.memo[i] = lengthreturn lengthdef lengthOfLIS(self, nums):self.memo = {}length = 1i = 0for i in range(i, len(nums)):length = max(length, self.L(nums, i))return length
这里可以看到每次进行递归的时候,先去备忘录(memo)里查查看是不是已经计算过了,如果计算过了,那么就直接用上回的结果就好了。这样提交后不会超时了,大概4.5秒左右。
在B站上搜了一个该题的讲解,发现这个题还可以倒着想:
这里以一个5元素的序列为例,那么递归过程如上图:
- 当求第0个元素的最长长度时,其实就是求出第1、2、3、4个元素为首元素的子序列的最长长度,再+1
- 当求第1个元素的最长长度时,其实就是求出第2、3、4个元素为首元素的子序列的最长长度,再+1
- 当求第2个元素的最长长度时,其实就是求出第3、4个元素为首元素的子序列的最长长度,再+1
- 当求第3个元素的最长长度时,其实就是求出第个元素为首元素的子序列的最长长度,再+1
- 当求第4个元素的最长长度时,因为只剩这1个元素,所以其长度就是1
这里可以直接看出重复了很多次计算,那我们先后往前求不就好了么!
代码如下:
class Solution:def lengthOfLIS(self, nums):n = len(nums)L = [1] * nfor i in reversed(range(n)):for j in range(i+1, n):if nums[j] > nums[i]:L[i] = max(L[i], L[j]+1)return max(L)
代码更短,但需要自己琢磨琢磨,明白了肯定会发出“哦~~”的。
这样的代码提交到力扣平台执行时间可以降到3秒左右。
但这仍然不是终点,老实说当我看到下面这张图的时候,我是崩溃的:
恕我无能,我是真的想不出几十毫秒的答案,我把评论区里最牛逼的答案贴在这儿,以示警醒:
class Solution {public int lengthOfLIS(int[] nums) {/**dp[i]: 所有长度为i+1的递增子序列中, 最小的那个序列尾数.由定义知dp数组必然是一个递增数组, 可以用 maxL 来表示最长递增子序列的长度.对数组进行迭代, 依次判断每个数num将其插入dp数组相应的位置:1. num > dp[maxL], 表示num比所有已知递增序列的尾数都大, 将num添加入dp数组尾部, 并将最长递增序列长度maxL加12. dp[i-1] < num <= dp[i], 只更新相应的dp[i]**/int maxL = 0;int[] dp = new int[nums.length];for(int num : nums) {// 二分法查找, 也可以调用库函数如binary_searchint lo = 0, hi = maxL;while(lo < hi) {int mid = lo+(hi-lo)/2;if(dp[mid] < num)lo = mid+1;elsehi = mid;}dp[lo] = num;if(lo == maxL)maxL++;}return maxL;}}
这位老哥已经写了注释,但我依然没看懂,后继还要继续加油啊。
Review
Tips
本周说一下golang吧,本周花了半天时间将原来用python写的一个图片叠加文字的小程序用golang改写了。说下感受:
- golang的单代码文件组织方式怎么都比C++高一大截
- 语法比较简洁,除了接口和指针部分有点迷惑以外,其他的都很容易上手
- 官方的库非常完整,所有的代码基本都可以直接拿官方库撸
- 静态打包单一exe,这是golang最大的优点了,也是我学golang的主要动机,想想C++的程序一堆依赖库,还要装不同版本的运行时库就头疼,而golang就容易多了,一句
go build main.go,直接就打包好exe了,把exe往别的机器上一扔就能跑,是真的方便!
不过golang也不是这么完美,代码跟python相比还是要啰嗦很多,甚至在改写的时候差点没坚持下来……另外golang中最出名的goroutine在这个小项目里没用上,没能体会到其最强大的地方。
推荐各位有C++基础,但苦于C++弱弱的标准库的同学们上手一下,也许下个项目可能你会想用golang来写哦^-^
Share
无。
这周又懒了,Review又没有,Share的文章也没写(其实上周也不算写)。
自我表达对于个人提升非常有帮助,不论是口头还是笔头,所以下周一定不能再懒了,有想写的题目,先列大纲,周末再扩展开来。
