给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = “abc”, t = “ahbgdc”
输出:true
示例 2:
输入:s = “axc”, t = “ahbgdc”
输出:false
1 双指针
初始化两个指针 slow和 fast,分别指向 s 和 t的初始位置。每次贪心地匹配,匹配成功则 slow 和 fast同时右移,匹配 s 的下一个位置,匹配失败则 fast 右移,slow不变,尝试用 t 的下一个字符匹配 s。最终如果 slow 移动到 s 的末尾,就说明 s是 t 的子序列。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
slow = 0
fast = 0
while (fast < len(t)) and (slow < len(s)):
if s[slow] == t[fast]:
slow += 1
fast += 1
else:
fast += 1
return slow == len(s)
还有一个后续挑战,需要检验大量的 s 是否是 t 的子序列。在上面的双指针的方法当中,从前往后去匹配字符需要大量的时间,那么这里再使用双指针的方法显然不合适。这里参考官方题解,说一下动态规划如何去快速匹配 s 是否是 t 的子序列。首先用动态规划的方法去进行预处理。
状态定义
设 dp[i][j] 表示字符串 t 中从 i 的位置开始往后匹配,字符 j 第一次出现的位置。
状态转移方程
1 如果 t 中位置 i 的字符就是 j 的话,那么 dp[i][j] = i;
2 若不是上面的情况,那么也就是说 j 出现在 i 位置之后的某个位置,此时 dp[i][j] = dp[i+1][j]
状态初始化
i 的取值范围为 [0, t_len),当 i = t_len-1 的时候,这里可能会无法进行转移,因此我们需要把dp的初始值设置成一个无效的位置len(t),这样最后在回溯的时候,如果dp的取值依然还是len(t)+1就说明没有匹配上
def issub(s, t):
# 初始值设置为无效值,因为t里面有效的位置为[0,len(t)-1]
dp = [[len(t)+1]*26 for _ in range(len(t))]
for i in range(len(t)-1, -1, -1):
for j in range(26):
if ord(t[i]) == ord('a') + j:
dp[i][j] = i
elif i == len(t) - 1:
continue
else:
dp[i][j] = dp[i+1][j]
idx = 0
# 开始遍历s,检验 s 的每个字符在 t 中的某个位置是否存在
for k in range(len(s)):
# 既然k还有效,说明s还没有匹配完毕,
# 但是这个时候如果idx已经越了t的界
# 说明整个匹配是不成功的,返回false
if idx > len(t) - 1:
return False
# 如果dp显示匹配s[k]的是一个t中的无效位置,表示无法匹配字符,那么返回 False
if dp[idx][ord(s[k])-ord('a')] == len(t) + 1:
return False
# 当匹配到当前字符s[k]之后,
# 从匹配到的dp[idx][ord(s[k])-ord('a')]这个位置的下一个位置(+1)开始,
# 查找s[k+1]是否出现在 t 中的某个位置
else:
idx = dp[idx][ord(s[k])-ord('a')] + 1
print(idx-1)
return True