给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
此处,s的长度不超过100, t的长度不超过10000,这两个字符串都只会有小写组成。
举例1:
输入:s = “abc”, t = “ahbgdc”
输出:true
举例2:
输入:s = “aaaaaa”, t = “bbaaaa”
输出:false
举例3:
输入:s = “afc”, t = “ahbgjc”
输出:false
解法一:使用索引进行比对
思路分析:主要是通过索引获取字符串片段进行查找比对。
首先判断字符串s和t是否为空,为空就返回false。不为空,就遍历s中的每个字符是否在在字符串t中,只要有一个字符串不在t中,那么就返回false。需要注意的是,为比避免s中重复的字符串出现在t中只会匹配到其第一个的位置,因此t的字符串需要不断减小,需要减去前面匹配过的部分,这样才符合子串的定义
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if s and t: # 如果s或中有空字符串,立即返回false
return False
else:
N = len(t) # t字符串的总长度
i = 0 # 表字符串开始匹配的位置
for j in s: # 遍历s中每个字符
if i >= N: # 字符匹配的位置不能超过子串的总长度
return False
if i not in t[i:j]: # 判断s中的字符是否在t中
return False
else:
start = t.index(j,i,N) + 1 # 在t[j:N]字符串的中查找是有s的字符后立马向后查询
return True
解法二: 使用双指针求解
思路分析:对于字符串s和字符串t各自设置一个指针i,j,从各自的首尾元素出发,然后进行比较,如果对应的元素相同,那么两个指针都向后移动一位,如果不相同,那么t的指针向后移动一位,直到遍历完s的所有元素。最后s的指针一定移动到了最后一个元素,此时的指针对应的数值如果与s的长度相等,那么返回true,否则返回false
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if s and t: # 如果s或中有空字符串,立即返回false
return False
else:
i = j = 0 # 设置两个指针,i指向s中的元素,j指向t中的元素,从首位开始
M = len(s)
N = len(t)
while i < M and j < N: # 当这两个指针都,没有超过各自的字符串长度时
if s[i] == t[j]: # 如果字符串s中的元素出现在字符串t中,那么i,j各自向后移动一位
i += 1
j += 1 # 如果字符串s中的元素没有出现在字符串t中,那么j向后移动一位
return(i == len(s)) # 只有当指针i最终的位置与字符串s的长度相等时,才返回true
解法三:动态规划方法
使用动态规划的方法对要比对的字符串t进行处理,令 dp[i][j]表示字符串t中从位置i开始往后字符 j 第一次出现的位置。在进行状态转移时,如果t 中位置i的字符就是j,那么dp[i][j]=i,否则j 出现在位置i+1 开始往后,即 dp[i][j]=dp[i+1][j],因此需要倒过来进行动态规划,从后往前枚举i。
这样我们可以写出状态转移方程:
d p [ i ] [ j ] = { i t[i]=j d p [ i + 1 ] [ j ] t[i]!=j dp[i][j]= \begin{cases} i& \text{t[i]=j}\\[2ex] dp[i+1][j]& \text{t[i]!=j}\\[2ex] \end{cases} dp[i][j]=⎩ ⎨ ⎧idp[i+1][j]t[i]=jt[i]!=j
假定下标从 0 开始,那么dp[i]][j] 中有$ 0 ≤i≤M−1$ ,对于边界状态 dp[M-1][…],我们置 dp[M][…]为 M,让 dp[M-1][…] 正常进行转移。这样如果 dp[i][j]=m,则表示从位置 i开始往后不存在字符j。
class Solution:
def isSubsequence2(self, s: str, t: str) -> bool:
if s and t: # 如果s或中有空字符串
return False
else:
N, M = len(s), len(t)
dp = [[0] * 26 for _ in range(M)] # 将字符串t的每个元素设置为打表器,行数为t的元素下标,列数为26个字母A-Z
dp.append([M] * 26) # 多设置一列,用M表示正无穷,也就是不存在
for i in range(M-1, -1, -1, -1):
for j in range(26):
if ord(t[j] == j +ord('a')):
dp[i][j] = i
else:
dp[i][j] = dp[i+1][j]
add = 0
for i in range(N):
if[add][ord(s[i]) - ord('a')] == M:
return False
add = dp[add][ord(s[i]) - ord('a')] + 1
return True
在字符串s较少时,使用双指针法更加节省时间,如果有大量的s需要查找,那么动态规划更加节省时间。因为双指针方法每次都需要遍历字符串t,而动态规划只需要一次求出打印表即可。