LeetCode刷题-字符串392:判断子序列

本文介绍了如何判断一个字符串是否是另一个字符串的子序列,提供了双指针、弹栈、生成迭代器、库函数以及二分法和动态规划等多种解法,并详细解释了每种方法的执行效率和思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给定字符串 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值