题目
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
示例1:
输入:s = "abc", t = "ahbgdc" 输出:true
示例2:
输入:s = "axc", t = "ahbgdc" 输出:true
提示:
0 <= s.length <= 100
0 <= t.length <= 10^4
- 两个字符串都只由小写字符组成
解题思路
注:下面法3、4、5引自LeetCode评论区powcai的解法
法1:双指针(执行用时20ms,内存消耗13MB)
初始化两个指针 i 和 j ,分别指向序列 s 和序列 t 的初始位置,从前往后遍历,每次对s[i] 和 s[j] 进行匹配,当匹配成功时 i 和 j 同时右移,继续匹配 s 的下一个位置,若匹配失败,则 j 右移,i 不变,继续尝试用 t 的下一个字符来匹配 s 。
如果最终 i 移动到了 s 的末尾,则说明 s 是 t 的子序列。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
i,j = 0,0
while i < len(s) and j < len(t):
if s[i]==t[j]:
i+=1
j+=1
if i == len(s):
return True
else:
return False
法2: 弹栈(执行用时28ms,内存消耗13.1MB)
把 t 遍历到栈中进行判断,初始化 s 的索引位置,每次把t的一个元素入栈,并与s的当前位置元素进行匹配,如果不等于 s 顺序元素,则弹出,否则将s的索引往后移;接着进行临界判断,当栈的长度与 s 的长度相等时,即 s 序列已完成遍历,结束循环;最后判断栈中元素是否与s相等即可。
此外,已知当 s 序列的长度为0时,其一定是 t 的子序列,因此为了节省判断次数,可以在开头先对s是否为空序列进行判断。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
if len(s) == 0 :
return True
stack_t = []
index_s = 0
for i in t:
stack_t.append(i)
if stack_t[-1] != s[index_s]:
stack_t.pop()
else:
index_s +=1
if len(stack_t) == len(s):
break
return ''.join(stack_t) == s
法3:生成迭代器(执行用时20ms,内存消耗13MB)
首先,我们将t
转换为一个迭代器,它的作用是c in t
通过迭代器进行迭代,直到找到匹配的第一个位置。 其次,c in t for c in s
是一个生成器:它返回一个迭代器。 第三,all()
将迭代器作为参数,并循环查找第一个False
。如果找不到,则返回True
all函数可以判定一个元组或者列表中的元素是否都为真,all(iterable),其中iterable-包含元素的任何可迭代(列表,元组,字典等)。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
t = iter(t)
return all(c in t for c in s)
法4:库函数(执行用时16ms,内存消耗12.9MB)
初始化 t 的位置为 -1,遍历 s 序列,每次利用 find() 函数从 t 中寻找 s 中元素第一次出现的位置,并判断index_t 的位置是否是-1,若是,说明没找到,返回False。
语法
str.find(str, beg=0, end=len(string))
参数
str---a指定检索的字符串
beg---开始检索,默认为0
end---结束检索,默认为字符串的长度
返回值
如果包含子字符串返回开始的索引值,否则返回-1。
class Solution(object):
def isSubsequence(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
index_t = -1
for a in s:
index_t = t.find(a,index_t+1)
if index_t == -1:
return False
return True
法5:二分法
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
from collections import defaultdict
import bisect
lookup = defaultdict(list)
for idx, val in enumerate(t):
lookup[val].append(idx)
# print(lookup)
loc = -1
for a in s:
j = bisect.bisect_left(lookup[a], loc + 1)
if j >= len(lookup[a]): return False
loc = lookup[a][j]
return True
法6:动态规划(DP)
5、6这两个方法有点不太好理解,先放上来想看就看吧,后面再研究下
考虑前面的双指针的做法,我们注意到我们有大量的时间用于在 tt 中找到下一个匹配字符。
这样我们可以预处理出对于 tt 的每一个位置,从该位置开始往后每一个字符第一次出现的位置。
我们可以使用动态规划的方法实现预处理,令 f[i][j]f[i][j] 表示字符串 tt 中从位置 ii 开始往后字符 jj 第一次出现的位置。在进行状态转移时,如果 tt 中位置 ii 的字符就是 jj,那么 f[i][j]=if[i][j]=i,否则 jj 出现在位置 i+1i+1 开始往后,即 f[i][j]=f[i+1][j]f[i][j]=f[i+1][j],因此我们要倒过来进行动态规划,从后往前枚举 ii。
这样我们可以写出状态转移方程:
f[i][j]={i,t[i]=jf[i+1][j],t[i]≠jf[i][j]={i,f[i+1][j],t[i]=jt[i]=j
假定下标从 00 开始,那么 f[i][j]f[i][j] 中有 0≤i≤m−10≤i≤m−1 ,对于边界状态 f[m−1][..]f[m−1][..],我们置 f[m][..]f[m][..] 为 mm,让 f[m−1][..]f[m−1][..] 正常进行转移。这样如果 f[i][j]=mf[i][j]=m,则表示从位置 ii 开始往后不存在字符 jj。
这样,我们可以利用 ff 数组,每次 O(1)O(1) 地跳转到下一个位置,直到位置变为 mm 或 ss 中的每一个字符都匹配成功。
同时我们注意到,该解法中对 tt 的处理与 ss 无关,且预处理完成后,可以利用预处理数组的信息,线性地算出任意一个字符串 ss 是否为 tt 的子串。这样我们就可以解决「后续挑战」啦。
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/is-subsequence/solution/pan-duan-zi-xu-lie-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
n, m = len(s), len(t)
f = [[0] * 26 for _ in range(m)]
f.append([m] * 26)
for i in range(m - 1, -1, -1):
for j in range(26):
f[i][j] = i if ord(t[i]) == j + ord('a') else f[i + 1][j]
add = 0
for i in range(n):
if f[add][ord(s[i]) - ord('a')] == m:
return False
add = f[add][ord(s[i]) - ord('a')] + 1
return True