1143. 最长公共子序列
本题相对于连续最长公共子序列而言,本题不要考虑连续问题,不同的是更新公式的不同。
之前一题,连续递推更新公式:
if nums1[i - 1] == nums2[j - 1]:
# 在当前位置上的最长公共子数组长度为前一个位置上的长度加一
dp[i][j] = dp[i - 1][j - 1] + 1
# 更新最长公共子数组的长度
if dp[i][j] > result:
result = dp[i][j]
可以看出,dp[i][j]的状态只能由前一个来推导出。也就是更新公式是唯一的,并且需要伴随的记录来保证存到最大值(因为不一定最后一个保存的是最长的)。
本题,即不需连续的公式:
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
可以看出,在原来的基础上加上了一个else的操作,也就是,如果当前的dp[i][j]在当前数字上不能被增加的话,就取上一个状态的最大值。
上一个状态就是分别在两个字符串中前一个位置的两个状态max(dp[i][j-1], dp[i-1][j]),一个异步的操作。
1、确定dp数组的含义
dp[i][j]表示在截止到text1和text2的前i-1和j-1的最大公共子序列的长度。
2、确定更新公式
按照上面的分析,更新公式可以分为:
(1)如果当前的i-1和j-1位置的字符相等,那么更新:dp[i][j] = dp[i-1][j-1] + 1;
(2)当前i-1和j-1的字符不相等,那么就取最大值:dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
3、确定初始化
在之前最大子串的初始化时,基于dp数组的含义,dp[0][0]是表示在没有开始匹配的时候最大的子串长度,所以全部是0。
4、确定遍历顺序
遍历顺序需要根据更新顺序来确定,因此应该是两个for循环内都是从小到大的顺序。
class Solution(object):
def longestCommonSubsequence(self, text1, text2):
"""
:type text1: str
:type text2: str
:rtype: int
"""
dp = [[0] * (len(text2) + 1) for _ in range(len(text1) + 1)]
for i in range(1, len(text1) + 1):
for j in range(1, len(text2) + 1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else: # 如果不匹配,则从之前两个状态中选最大的长度
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
说明
关于最后返回值的大小,本题区分于之前的题目在于不需要创建一个伴随值来记录长度,因为不连续的子串长度可以一直保存到最后。所以返回值是dp[-1][-1]。
1035. 不相交的线
对于两个不想交的直线,也是就是说nums1在nums2对应的点必须顺序一致,也就是顺序(不连续)的子序列。
这个公共子序列指的是相对顺序不变(即数字4在字符串A中数字1的后面,那么数字4也应该在字符串B数字1的后面)。
所以和上一题是完全一样的思路和题解。就不过多解释了。
class Solution(object):
def maxUncrossedLines(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: int
"""
dp = [[0] * (len(nums2) + 1) for _ in range(len(nums1) + 1)]
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
else:
dp[i][j] = max(dp[i][j-1], dp[i-1][j])
return dp[-1][-1]
53. 最大子数组和
本题需要在连续的基础上做一个买股票的动态规划。
1、dp数组的含义
dp[i]一维数组,表示当前的位置i处,所存在的最大子串和,注意是必须包含当前i索引的数字;
2、dp数组的更新
(1)加上当前nus[i]数字后,比前一个dp[i-1]大,则当前dp值为前一个dp加上当前数字;
(2)加上当前数字之后比之前的dp值小,则从当前数字开始从头计算。
3、dp数组的初始化
由于dp的更新只跟前一个dp有关系,所以dp的初始化就是dp[0] = nums[0]。
4、确定遍历顺序
遍历顺序是从小到大即可。
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return 0
dp = [0] * len(nums)
dp[0], res = nums[0], nums[0]
for i in range(1, len(nums)):
dp[i] = max(dp[i-1] + nums[i], nums[i])
res = max(res, dp[i])
return res
392. 判断子序列
判断是否是子序列相当于是判断是否包含相同子序列的操作中,要求短字符串(s)进行全匹配,也就是不进行删除,最后判断dp数组最后一个值是否和s的长度相等,如果相等,输出true,如果不相等,则输出false。
因此,本题的关键就是在更新dp的时候如何保持s中的index不变,只改变t中的index进行更新。
1、dp数组的含义
dp数组的含义其实和之前的最长子串的含义一致,都是在i-1,j-1处的最长公共子序列长度。为什么不是i,j,是因为初始化的方便以及dp更新的时候依赖i-1和j-1的dp值。
2、确定dp更新公式
更新公式依然是根据当前i-1和j-1所处位置的字符是否一致来确定。
(1)如果当前的字符相等,则dp[i][j] = dp[i-1][j-1] + 1;
(2)如果当前字符不相等,则更新为上一个dp值。
注意,这里的更新为上一个dp值而不是max是因为s不能进行退位(index退回到i-1),是因为s需要进行全部的匹配,因此当前的i不能被跳过或者是进行删除,而必须被“保留”或者hold住。同时更改t序列中j的值进行回退。所以,dp[i][j] = dp[i][j-1]。
3、确定初始化
最长公共子序列的初始化一致,都是初始化为0。
4、确定遍历顺序
和最长公共子序列一样,i,j都是从小到大进行遍历。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
dp = [[0] * (len(t) + 1) for _ in range(len(s) + 1)]
for i in range(1, len(s) + 1):
for j in range(1, len(t) + 1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = dp[i][j-1] # 这里不需要max(dp[i-1][j]是因为必须要s全部参与匹配,不能跳过(删除)一些元素)
return dp[-1][-1] == len(s)
Day50完结!!!