300.最长递增子序列
五部曲:
1. dp[i]的定义:dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度
2. 确定递推公式:位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 +1 的最大值。
- 为什么要+1?如果nums[j]也就是nums[i-1] > nums[i]的时候,这时候就不能+1?
- 所以在取最大值之前要判断 if (nums[i] > nums[j]),即 if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
- 注意这里是要取dp[j] + 1的最大值
3. dp[i]的初始化:每一个i,对应的dp[i](即最长上升子序列)起始大小至少都是1
4. 确定遍历顺序:dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。
5. 打印检查
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if len(nums) <= 1:
return len(nums)
dp = [1]*len(nums)
res = 0
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j]+1)
res = max(res, dp[i])
return res
674. 最长连续递增序列
相较于#300,这道题是求最长连续递增序列,这样就排除了通过删除元素进而组成更长的递增子序列的情况(#300的情况)
五部曲:
- dp数组的含义:dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。
- 确定递推公式:如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1 。即 if nums[i + 1] > nums[i]: dp[i + 1] = dp[i] + 1;
- dp数组如何初始化:dp[i] = 1
- 确定遍历顺序:从递推公式上可以看出,dp[i + 1]依赖dp[i],所以是从前向后遍历。
- 打印检查
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
if len(nums) <= 1:
return len(nums)
dp = [1]*len(nums)
res = 0
for i in range(len(nums)-1):
if nums[i+1] > nums[i]:
dp[i+1] = dp[i] + 1
res = max(res, dp[i+1])
return res
718. 最长重复子数组
- 注意题目中说的子数组,其实就是连续子序列!
五部曲:
- 确定dp数组以及下标的含义:dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。(特别注意: “以下标i - 1为结尾的A” 表明一定是 以A[i-1]为结尾的字符串 )
- 确定递推公式:
- 根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
- 即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;(因为dp[i - 1][j - 1]以下标i - 2为结尾的A,和以下标j - 2为结尾的B的最长重复子数组长度,那么由于A[i - 1] == B[j - 1],因此就需要再 + 1)
- 根据递推公式可以看出,遍历i 和 j 要从1开始!
- dp数组如何初始化:
- 根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的!
但dp[i][0] 和dp[0][j]要初始值,因为为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1;
所以dp[i][0] 和dp[0][j]初始化为0。
- 确定遍历顺序:从前向后遍历,先遍历A或者B都可以
- 打印检查
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
dp = [[0] * (len(nums2)+1) for _ in range(len(nums1)+1)]
result = 0
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
result = max(result, dp[i][j])
return result
因为dp数组的定义是关于i-1和j-1的,所以才需要长度为len()+1
(Trick:其实在定义数组的时候可以直接多加一个1,因为空间上不会影响多少,并且也避免了可能的错误,类似这道题按照定义就必须+1)
- 可以看出dp[i][j]都是由dp[i - 1][j - 1]推出。那么压缩为一维数组,也就是dp[j]都是由dp[j - 1]推出。
- 注意:此时遍历B数组(或者说内循环)的时候,就要从后向前遍历,(参考滚动数组遍历顺序)。
class Solution(object):
def findLength(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: int
"""
dp = [0] * (len(nums2) + 1)
result = 0
for i in range(1, len(nums1)+1):
for j in range(len(nums2), 0, -1):
if nums1[i-1] == nums2[j-1]:
dp[j] = dp[j-1] + 1
else:
dp[j] = 0 #注意这里不相等的时候要有赋0的操作
result = max(result, dp[j])
return result
- 像#300和#674这两道题对于dp数组的定义就比较直观(以下标i为结尾的数组),但是这道题的定义要以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。(虽说carl也提到了将dp[i][j]定义成以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度也是可以的;但是比较复杂,具体如何复杂没有细讲,所以对于这道题的dp数组定义并不好想。)
- 可以实现dp数组定义是以下标 i 为结尾的A,和以下标 j 为结尾的B,最长重复子数组长度为dp[i][j]。
- 但需要initialized the first row and the first column
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
dp = [ [0]*(len(nums2)) for _ in range(len(nums1)) ]
res = 0
# initialized the first row and the first column
for i in range(len(nums1)):
if nums1[i] == nums2[0]:
dp[i][0] = 1
res = max(res, 1)
for j in range(len(nums2)):
if nums2[j] == nums1[0]:
dp[0][j] = 1
res = max(res, 1)
for i in range(1, len(nums1)):
for j in range(1, len(nums2)):
if nums1[i] == nums2[j]:
dp[i][j] = dp[i-1][j-1] + 1
res = max(res, dp[i][j])
return res