文章目录
392. 判断子序列
题目链接 | 解题思路
乍一看本题和之前的题没什么关系,只是一道简单的双指针。但实际上,这是一道特殊版本的最长公共子序列,同时也是编辑距离的入门。作为编辑距离的题,本题只考虑删除元素即可。
dp - 编辑距离入门
题目要求判断 s 是否是 t 的子序列,其实相当于要求 s 和 t 的最大公共子序列的长度就是 len(s)
。
-
dp 数组的下标含义:
dp[i][j]
是s[:i+1]
和t[:j+1]
的最大公共子序列的长度 -
dp 递推公式:
-
如果
s[i] == t[j]
,可以得到dp[i][j] = dp[i-1][j-1] + 1
-
否则,我们就考虑
t[:j]
从而得到dp[i][j] = dp[i][j-1]
(代码随想录的递推公式)- 这里有一个比较令人费解的状态递推:为什么直接得到
dp[i][j] = dp[i][j-1]
,而不是像之前一样dp[i][j] = max(dp[i][j-1], dp[i-1][j])
? - 这涉及到了利用最大公共子序列的长度来进行本题的抽象化的一个要求:这个抽象化的结果要和
len(s)
进行比较- 举例:
s = "abc", t = "ab"
,按照当前的递推公式dp[2][1] = dp[2][0] = 0
,按照之前的递推公式dp[2][1] = max(dp[1][1], dp[2][0]) = 2
,这两个结果明显不同,但是在比较dp[2][1] == len(s)
时给出的结果都是False
- 举例:
s = "ab", t = "abe"
,按照当前的递推公式dp[1][2] = dp[1][1] = 2
,按照之前的递推公式dp[2][1] = max(dp[1][1], dp[2][0]) = 2
,这两个结果相同,在比较dp[2][1] == len(s)
时给出的结果都是True
- 从这两个例子可以看出,两种递推公式似乎都不影响结果,但
s = "abc", t = "ab"
时得到dp[2][1] = 0
是明显不符合当前定义的。
- 举例:
- 这里有一个比较令人费解的状态递推:为什么直接得到
-
所以根据当前定义,当
s[i] != t[j]
时,还是使用dp[i][j] = max(dp[i][j-1], dp[i-1][j])
作为递推公式。
-
-
dp 数组的初始化:对于当前的定义,初始化和之前是一样的,都是针对第一行和第一列进行单独的初始化
-
dp 的遍历顺序:从上到下,从左到右。满足当前位置的左上角已经解决即可。
-
举例推导:
s = "abc", t = "ahbgdc"
a h b g d c a 1 1 1 1 1 1 b 1 1 2 2 2 2 c 1 1 2 2 2 3
以下代码解的 i
和 j
的含义是反的,但不影响解题。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if len(s) == 0:
return True
if len(t) == 0:
return False
# dp[i][j] is the max length of common subarray between t[:i+1] and s[:j+1]
dp = [[0] * len(s) for _ in range(len(t))]
first_col_flag = False
for j in range(len(s)):
if t[0] == s[j]:
first_col_flag = True
if first_col_flag:
dp[0][j] = 1
first_row_flag = False
for i in range(len(t)):
if t[i] == s[0]:
first_row_flag = True
if first_row_flag:
dp[i][0] = 1
for i in range(1, len(t)):
for j in range(1, len(s)):
if t[i] == s[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] == len(s)
dp - 传递 bool 来确定编辑
上面对于 dp[i][j] = dp[i][j-1]
的递推公式进行了很长的讨论。根据纯粹的最长公共子序列的长度的定义,这个递推公式显得不正常。但是如果用如下的 dp 定义,就能意识到利用这个递推公式的情景。
-
dp 数组的下标含义:
dp[i][j]
记录了s[:j+1]
是否是t[:i+1]
的子序列(bool
) -
dp 递推公式:
- 如果
s[j] == t[i]
,那么显然可以直接继承之前的状态dp[i][j] = dp[i-1][j-1]
- 否则,
dp[i][j] = dp[i-1][j]
- 根据定义中要求判断
s[:j+1]
,很明显不能利用dp[i][j-1]
,因为即使确定s[:j]
是t[:i+1]
的子序列,在s[j] != t[i]
的情况下也不能判断s[:j+1]
是否是t[:i+1]
的子序列 - 而对于
dp[i-1][j]
,既然s[j] != t[i]
,那么t[i]
就不能在包含子序列这个情况下产生任何作用,自然应该直接继承dp[i-1][j]
- 根据定义中要求判断
- 如果
-
dp 数组的初始化:由于我们在递推中用到了
dp[i-1][j]
和dp[i-1][j-1]
,当然需要初始化第一行和第一列- 对于
j=0
,只要t[:i+1]
包含了s[0]
,第一列接下来所有的值都应该是True
- 对于
i>0
,t[0]
明显没有可能包含s[:i+1]
,第一行所有的值都设为False
即可,也就不需要额外初始化
- 对于
-
dp 的遍历顺序:从上到下,从左到右即可。
-
举例说明:
s = "abc", t = "ahbgdc"
a h b g d c a True True True True True True b False False True True True True c False False False False False True
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if len(s) == 0:
return True
if len(t) == 0:
return False
# dp[i][j] represents whether s[:j+1] is a subsequence of t[:i+1]
dp = [[False] * len(s) for _ in range(len(t))]
row_flag = False
for i in range(len(t)):
if t[i] == s[0]:
row_flag = True
if row_flag:
dp[i][0] = True
for i in range(1, len(t)):
for j in range(1, len(s)):
if t[i] == s[j]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = dp[i-1][j]
return dp[-1][-1]
双指针
最朴实、直观的解法。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if len(s) == 0:
return True
sub_idx = 0
for idx in range(len(t)):
if t[idx] == s[sub_idx]:
sub_idx += 1
if sub_idx == len(s):
return True
return False
115. 不同的子序列
题目链接 | 解题思路
看到多少种方法 + 子序列,第一反应是回溯所有的子序列,然而数据量很明显不可能支持回溯,题目难度也没有到必须暴力搜索的地步。
如果本题需要的是连续子数组,那么可以考虑 KMP。
-
dp 数组的下标含义:
dp[i][j]
是s[:i+1]
的所有子序列中和t[:j+1]
相同的子序列的数量 -
dp 递推公式:
- 如果
s[i] != t[j]
,那么就不能用s[i]
参与子序列和t[:j+1]
的匹配,dp[i][j] = dp[i-1][j]
- 如果
s[i] == t[j]
,那么还要加上利用s[i]
参与子序列和t[:j+1]
的匹配数量,dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
- 如果
-
dp 数组的初始化:根据递推公式的要求,还是要初始化第一行和第一列
j=0
,则dp[i][0]
就是s[:i+1]
中包含的t[0]
的数量i=0
,则dp[0][j] = 0
( j > 0 j>0 j>0),因为s
不可能包含比自己长的子序列,所以不需要额外初始化
-
dp 遍历顺序:从上到下、从左到下即可
-
举例推导:
s = "rabbbit", t = "rabbit"
r a b b i t r 1 0 0 0 0 0 a 1 1 0 0 0 0 b 1 1 1 0 0 0 b 1 1 2 1 0 0 b 1 1 3 3 0 0 i 1 1 3 3 3 0 t 1 1 3 3 3 3
class Solution:
def numDistinct(self, s: str, t: str) -> int:
# dp[i][j] represents the number of distinct subsequences of s[:i+1] equals to t[:j+1]
dp = [[0] * len(t) for _ in range(len(s))]
dp[0][0] = 1 if t[0] == s[0] else 0
for i in range(1, len(s)):
dp[i][0] = dp[i-1][0]
if t[0] == s[i]:
dp[i][0] += 1
for i in range(1, len(s)):
for j in range(1, len(t)):
dp[i][j] = dp[i-1][j]
if s[i] == t[j]:
dp[i][j] += dp[i-1][j-1]
return dp[-1][-1]