题目链接

https://leetcode-cn.com/problems/longest-increasing-subsequence/

题目介绍


最长递增子序列

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


动态规划

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        #初始化并且定义dp
        dp = [1] * n
        for i in range(n):
            for j in range(i):
                if nums[i] > nums[j]:
                #自对抗储存前面大的
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp) #万一前面已经结束且比末元素大

思路


本题跟前面讲过的最大连续递增子序列有点像,但也有区别,区别在于上一题要求连续,而本题不用连续也可。

首先,需要理解本题的框架,两层for loop,先确定一个i,然后ji的范围内不断增加。本题要求可以不是连在一起的,所以,这个for loop的原因在于需要找到对于一个特定的i,究竟有多少个j,满足dp[j] < dp[i],这样的话,找到最大的小于dp[i] 的子序列最后加上1即可。

其次,我相信应该有人很难理解

dp[i] = max(dp[i],dp[j] + 1)

这个语句的意思是把每一个的子序列长度求出来。会不会有人这样想,dp[i] = dp[j] + 1 不也一样吗?

举个例子,[3,4,1,7],按照上面想的,我们会发现dp[-1] = 2,实则为3,为什么呢?因为当确定一个i,j在一定范围内循环的时候,每一次发现比i小的 dp[i] 都会被重置一下,而题解中,无论何时,dp[j] + 1 > dp[i] 注意,这里的 dp[i] 指的是初始的,所以意思即为找出以小于 nums[i] 数字结尾的最长子序列

有人又会问了,那结尾为什么不能直接 dp[n] 呢?

举个例子,[1,3,6,7,9,4,10,5,6],按照你想的,求出来是以5结尾的子序列长度加1,可实际情况是不要6,直接以10结尾就已经满足了。求出来的dp = [1,2,3,4,5,3,6,4,5]。所以要返回整个数组的最大值

对比前面那道题,因为本题可以不连续,所以多了个for loop


拓展

如何理解高度抽象的代码呢?

Tip1

先自己将题目刷一遍,找到自己有疑惑的点,这样看答案时更有针对性

Tip2

看代码的时候甚至每一个细节都不能放过,就像上面将的max(dp)一个小小的max里面就有很多学问

Tip3

看答案先不要急着每一个细节都看懂,首先读完答案你脑海里应该有一个代码的框架,每一部分大致的意思你应该心里有数

其次,理解细节处的时候,可以试着解释,解释不通,就自己找几个实例,化抽象为具体,自己去演算一遍,或者合适的地方加一个print或者return语句(注意,实例不能都是一个类型,比如说本题,你就应该选几个有代表性的实例,先增后减,先减后增,全增或全减)

最后,你通过实例理解之后,不能就此罢休,你应该已经知道每一句代码的内涵了。这个时候,你要学会预估这个算法的时间空间复杂度,学会增添和替代,只有自己将答案代码改掉,做到神似而形不似的时候,才是真正理解

不要出现死磕代码,看半天不懂,还觉得自己很努力的现象,应该将努力去聚焦到一个一个可以把握的点

不要让问题看到明天早晨的太阳,出现问题,必须将此问题作为首要任务,想尽办法解决它,最迟不超过明天晚上

小故事

有一天记者去采访拳王阿里,看他做了半天的仰卧起坐,没忍住问了一句做了多少个,

:不知道,我只有在身体感到疼痛时才开始数

我们学算法和读代码也是一样,不要记住读懂多少个很显而易见的代码,而是那些一开始你根本看不懂没有头绪的代码后来你是怎么这么一步一步读懂的,最后,记住那个过程,因为往往这种过程都是可复制的,可以联系递归。