本题是子序列一套的开始。
本题中,正确定义dp数组的含义十分重要。
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度。
每一个数字都可以独立构成一个子序列,所以数组初始化全部为1.
在本题的遍历过程中,由于序列构成子序列是不连续删除构成的,所以递推公式不能确定为由之前某一个状态直接推到而来,所以在递推的公式中,需要进行遍历,再遍历中不断保存最优解。
if nums[i] > nums[j]: # 如果当前数字和i可以构成子序列
dp[i] = max(dp[i], dp[j] + 1) # 更新i的最大长度
外层遍历:遍历序列,i控制,从小到大
内层遍历:遍历0到i-1的子序列,j控制,从小到大
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = [1] * len(nums) # 全部初始化为1,因为最小长度就是1
for i in range(1, len(nums)):
for j in range(0, i): # 从0开始找i内部的全部子序列
if nums[i] > nums[j]: # 如果当前数字和i可以构成子序列
dp[i] = max(dp[i], dp[j] + 1) # 更新i的最大长度
return max(dp)
本题在上一题的基础上进行了限制:只能连续才能更新dp。
实际上就是在dp更新上做了变化:
if nums[i] > nums[i-1]:
dp[i] = max(dp[i], dp[i-1] + 1) # 这里只和前一个数字比较,所以不需要嵌套for循环
由于不需要考虑不连续的形式,所以dp的更新只要和之前一个数字的dp + 1比就可以了。
class Solution(object):
def findLengthOfLCIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = [1] * len(nums)
for i in range(1, len(nums)):
if nums[i] > nums[i-1]:
dp[i] = max(dp[i], dp[i-1] + 1) # 这里只和前一个数字比较,所以不需要嵌套for循环
return max(dp)
本题在第一题的基础上加上了一个匹配的环节。
关于本题,其实需要定义一个二维数组,因为需要在遍历一个数组的同时去遍历另外一个数组。
基于初始化的考虑,在更新的时候需要调用前一次,而直接初始化数组第一个位置的dp数组比较麻烦,相当于需要先来遍历一遍了。所以基于此,在dp数组的定义上稍加改动。
dp[i][j]表示以nums1的i-1和nums2的j-1为终止位置的最大重复子数组的长度。
这样在初始化的时候较为方便。
很简单,把递增的判定条件改为==即可。
如果当前的条件满足,则当前的dp为上一个位置的dp+1。
为什么i、j是同步为i-1、j-1?
因为在匹配的时候,两个位置是共进退的(因为需要连续匹配,不是间隔)。
由于dp的定义整体前移,所以在初始化的时候需要在行列数量上各加一。然后全部置0。
两层循环都是从小到大而且i和j是等价的,可以互换内外。
class Solution(object):
def findLength(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: int
"""
dp = [[0] * (len(nums2) + 1) for _ in range(len(nums1) + 1)]
res = 0 # res来保存每一个最大的可能值
for i in range(1, len(nums1)+1):
for j in range(1, len(nums2)+1):
if nums1[i-1] == nums2[j-1]:
dp[i][j] = dp[i-1][j-1]+1
res = max(dp[i][j], res) # 更新res
return res
这里还想说一下关于输出。
因为dp下标的含义,所以不一定是保证取到最后一个数字的时候就恰好是最优解,需要在过程中不断去更细全局最优解(因为可能出现在数组中间位置)。
所以最后的输出不是return max(dp[-1]),而是另外储存每一个最大值。
Day49完结!!!
这里有一个想法
二刷之前希望先学会时间和空间复杂度的计算和比较,卡玛网上面每一个解法都有对应的时空复杂度,这样二刷的时候相当于把这块练手了。