题目链接 | 解题思路
Subsequence: a string is a new string generated from the original string with some characters (can be
none
) deleted without changing the relative order of the remaining characters.
本题和之前的最长重复子数组很相似,区别仅仅只是子序列并不像子数组一样要求连续。但实际上,这个区别直接影响了 dp 数组的定义。
dp 数组的下标含义:dp[i][j]
是 text1[:i+1]
和 text2[:j+1]
的最长公共子序列长度
text1[i]
, text2[j]
结尾这个条件,可以直接进行正常的状态转移dp 递推公式:
text1[i] == text2[j]
,那么显然 dp[i][j] = dp[i-1][j-1] + 1
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
dp 数组的初始化:由于在递推公式中可能用到了 dp[i-1][j], dp[i][j-1]
,显然我们要初始化第一列 dp[i][0]
和第一行 dp[0][j]
i=0
的时候,根据定义,dp[0][j]
是 text1[0]
和 text2[:j+1]
的最长公共子序列的长度,所以如果 text1[0] == text2[k]
,这一行之后所有的 dp[0][j]=1
( j ≥ k ) (j\geq k) (j≥k)j=0
的时候,同理dp 数组的遍历顺序:从左到右、从上到下,正常遍历即可
举例推导:text1 = "abcde", text2 = "ace"
a | c | e | |
---|---|---|---|
a | 1 | 1 | 1 |
b | 1 | 1 | 1 |
c | 1 | 2 | 2 |
d | 1 | 2 | 2 |
e | 1 | 2 | 3 |
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# dp[i][j] represents the max common-subsequence length between text1[:i+1] and text2[:j+1]
dp = [[0] * len(text2) for _ in range(len(text1))]
first_row_flag = first_col_flag = False
for i in range(len(text1)):
if text1[i] == text2[0]:
first_row_flag = True
if first_row_flag:
dp[i][0] = 1
for j in range(len(text2)):
if text1[0] == text2[j]:
first_col_flag = True
if first_col_flag:
dp[0][j] = 1
result = 1 if first_row_flag or first_col_flag else 0
for i in range(1, len(text1)):
for j in range(1, len(text2)):
if text1[i] == text2[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
if dp[i][j] > result:
result = dp[i][j]
return result
注意,由于这里的 dp 数组定义发生了变化,最终的结果一定是 dp[-1][-1]
。所以不需要在遍历过程中不断记录最大值了。
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# dp[i][j] represents the max common-subsequence length between text1[:i+1] and text2[:j+1]
dp = [[0] * len(text2) for _ in range(len(text1))]
first_row_flag = first_col_flag = False
for i in range(len(text1)):
if text1[i] == text2[0]:
first_row_flag = True
if first_row_flag:
dp[i][0] = 1
for j in range(len(text2)):
if text1[0] == text2[j]:
first_col_flag = True
if first_col_flag:
dp[0][j] = 1
for i in range(1, len(text1)):
for j in range(1, len(text2)):
if text1[i] == text2[j]:
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]
题目链接 | 解题思路
第一次看到这道题目的描述真的被吓傻了,看上去甚至像是一道数学题。由此可见,对题目的抽象能力,有时候和解题能力一样重要。
来自代码随想录:直线不能相交,这就是说明在字符串 A 中找到一个与字符串 B 相同的子序列,且这个子序列不能改变相对顺序:只要相对顺序不改变,链接相同数字的直线就不会相交。
经过上述抽象化,可以发现,本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!和上一题完全一样,只要改一下变量名即可。然而想要完成这样的抽象化真的需要敏锐的嗅觉和对 dp 的深刻理解啊。
不过值得一提的是,我在解题时虽然没有直接想到这个抽象化方法,但是也能够通过 nums1[i]
和 nums2[j]
的比较来得到正确的递推公式(dp 数组的定义还是比较好想的)。显然,我们会很希望 nums1[i] == nums2[j]
,这样就可以直接使用之前的 dp[i-1][j-1]
。否则,我们只能希望去掉其中一个末尾元素可以找到“尾部连线”的状态。在得到正确的递推公式之后,也就能够逐渐理解题意了。
class Solution:
def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j] represents the max number of crosslines between nums1[:i+1] and nums2[:j+1]
dp = [[0] * len(nums2) for _ in range(len(nums1))]
first_row_flag = first_col_flag = False
for i in range(len(nums1)):
if nums1[i] == nums2[0]:
first_row_flag = True
if first_row_flag:
dp[i][0] = 1
for j in range(len(nums2)):
if nums1[0] == nums2[j]:
first_col_flag = True
if first_col_flag:
dp[0][j] = 1
result = 1 if first_row_flag or first_col_flag else 0
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
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
if dp[i][j] > result:
result = dp[i][j]
return result
题目链接 | 解题思路
之前用贪心算法解过,但实际上贪心算法比 dp 还要复杂。虽然是一道一维的子序列,但还是很好体现了 dp 利用当前元素递推子问题的特性。
dp[i]
记录的是 nums[:i+1]
中以 nums[i]
为结尾的最大连续子数组的和dp[i] = max(dp[i-1] + nums[i], nums[i])
dp[i]
可以由 dp[i-1]
直接推导,只需要关注 dp[0]
的初始化即可。根据定义,显然 dp[0] = nums[0]
[-2,1,-3,4,-1,2,1,-5,4]
-2 | 1 | -3 | 4 | -1 | 2 | 1 | -5 | 4 |
---|---|---|---|---|---|---|---|---|
-2 | 1 | -2 | 4 | 3 | 5 | 6 | 1 | 5 |
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [float('-inf')] * len(nums)
dp[0] = nums[0]
for i in range(1, len(nums)):
dp[i] = max([dp[i-1] + nums[i], nums[i]])
return max(dp)
在我做贪心算法的时候我就用这个 dp 解出来了,难道我真的是天才?