Leetcode刷题笔记-字符串总结

首先明确两个概念:子串与子序列。
比如一个字符串“aaabbc”的一个子串为“aaa”。而“abc”是它的一个子序列。即子串必须是在字符串中连续的,而子序列可不连续,但在字符串中的索引需要是升序的(“cba”就不是子序列)。

  • 回文字符串
    回文字符串指正序遍历和逆序遍历完全相同的字符串。
    一个简单的题目认识回文字符串。
  1. “回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。花花非常喜欢这种拥有对称美的回文串,生日的时候她得到两个礼物分别是字符串A和字符串B。现在她非常好奇有没有办法将字符串B插入字符串A使产生的字符串是一个回文串。你接受花花的请求,帮助她寻找有多少种插入办法可以使新串是一个回文串。如果字符串B插入的位置不同就考虑为不一样的办法。
    例如: A = “aba”,B = “b”。这里有4种把B插入A的办法:
* 在A的第一个字母之前: "baba" 不是回文
* 在第一个字母‘a’之后: "abba" 是回文
* 在字母‘b’之后: "abba" 是回文
* 在第二个字母'a'之后 "abab" 不是回文 所以满足条件的答案为2
def solution(A, B):
    res = 0
    for i in range(len(A)+1):
        tmp = A[:i]+B+A[i:]
        if tmp == tmp[::-1]:  # 判断正序遍历和逆序是否相同
            res += 1
    return res
  1. 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。
    返回 s 所有可能的分割方案
def solution(s):
    def dfs(rest, path):
        # path存储回文子串
        if rest == "":
            res.append(path)
            return
        for i in range(len(rest)):
            # 返回 s 所有可能的分割方案,是一个排列问题
            if rest[:i+1] == rest[:i+1][::-1]:  # 只有当前子串是回文时,才计入path
                dfs(rest[i+1:], path + [rest[:i+1]])
            return
    res = []
    return dfs(s, [])
  1. 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。
    返回 最小的分割次数。s长度很长!这时再用暴力解法无法通过。
    此时通过预处理以数组存储s[i][j]是否回文,避免反复判断是否回文。
    最小分割次数:
    状态:
    f[i]表示以i结尾的子串分割为回文子串的最小分割次数。
    转移方程:
    if s[0, i]是回文的:
    f[i] = 0
    else:
    此时要向前检查0到i-1的区间是否存在能与i形成回文子序列
    f[i] = 1 + f[i-1]
    表示i自己被单独分割一次的情况
    for j in range(i):
    if s[j, i]是回文子串:
    此时i-j作为一个回文串分割一次,加上0-j-1的最小分割次数
    f[i] = min(f[i], 1+f[j-1])
def solution(s):
    dp = [[0]*len(s) for _ in range(len(s))]
    dp[0][0] = True
    def check(s):
        # dp[i][j]存储从i-j的子串是否是回文的
        for r in range(len(s)):  # 右边的索引
            for l in range(r+1):  # l要取到r
                if r == l:
                    dp[l][r] = True
                    continue
                if s[r] == s[l]:
                    if (l+1 <= r-1 and dp[l+1][r-1] is True) or r-l == 1:
                        dp[l][r] = True
                    else:
                        dp[l][r] = False
                else:
                    dp[l][r] = False
        return
    min_split = [float('inf')]*len(s)  # 表示以i结尾的子串最少要分割几次
    min_split[0] = 0
    for i in range(1, len(s)):
        if dp[0][i] is True:  # 0-i是一个回文串
            min_split[i] = 0
        else:  # 非回文,分割子区间,取最小的分割值,前面的回文串最长时
            tmp = float('inf')
            tmp = min(tmp, 1+min_split[i-1])  # i单独分割一次
            # 或者与前面的某个位置形成回文,一起分割
            for j in range(1, i):
                if dp[j][i] is True:
                    tmp = min(tmp, 1 + min_split[j-1])
            min_split[i] = tmp
	return min_split[-1]         
  • 字符串编辑问题
  1. 小摩手里有一个字符串A,小拜的手里有一个字符串B,B的长度大于等于A,
    所以小摩想把A串变得和B串一样长,这样小拜就愿意和小摩一起玩了。
    而且A的长度增加到和B串一样长的时候,对应的每一位相等的越多,
    小拜就越喜欢。比如"abc"和"abd"对应相等的位数为2,为前两位。
    小摩可以在A的开头或者结尾添加任意字符,使得长度和B一样。
    现在问小摩对A串添加完字符之后,不相等的位数最少有多少位?

这类对字符串a加上一个字符的操作,基本上可以转化为对字符串b删去一个字符来求解。
问题转化为b的开头或结尾删去一个字母,使得不相等的位数最小。

def solution(A, B):
    dp = {}  # 存储b对应最小不相等位数
    def dfs(a, b):
        if len(a) == len(b):  # b的长度与a相等判断不相等位数
            n = len(a)
            st = 0
            count = 0
            while st<n:
                if a[st] != b[st]:
                    count += 1
                st += 1
            return count
        if dp.get(b):  # 避免重复搜索
            return dp[b]
        ans = float('inf')
        ans = min(ans, dfs(a, b[1:]), dfs(a, b[:-1]))  # 分别搜索b删除首字母和尾字母的结果
        dp[b] = ans
        return ans
    return dfs(A, B)
  1. 字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符
    给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
    示例 1:
    输入:
    first = “pale”
    second = “ple”
    输出: True
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/one-away-lcci
    因为编辑次数最大为1,按字符串的长度分情况:
    1. 长度之差大于1,无法一次编辑使二者相同,false
    2. 长度相等,仅能做替换操作,记录相同位点的不同字符数,若大于1,false
    3. 长度之差为1,对长的字符串删除一个字符后看能否和短字符串相同
def oneEditAway(self, first: str, second: str) -> bool:
    gap = abs(len(first)-len(second))
    if gap > 1:
        return False
    elif first == second:
        return True
    if len(first) < len(second):  # 使first是较长的字符串
        first, second = second, first
    n = len(first)
    m = len(second)
    count = 0
    if n-m == 0:
        for i in range(n):
            if first[i] != second[i]:
                count += 1
        return count <= 1
    else:
        for i in range(n):  # 删除first中的每一字母
            if first[:i]+first[i+1:] == second:
                return True
        return False
  • 字符串匹配问题
    给定一个待匹配字符串string,与要在string中查找的pattern。要求返回是否存在pattern或数量或string的索引。
    字符串匹配问题可以用KMP算法求解,或者通过动态规划求解。
    动态规划法:
    参考自AC_OIer的回答
    链接:https://leetcode-cn.com/problems/distinct-subsequences/solution/xiang-jie-zi-fu-chuan-pi-pei-wen-ti-de-t-wdtk/
    对于两个字符串匹配,一个非常通用的状态定义如下:
    定义 f[i][j]为考虑 string中[0, i] 个字符,pattern中[0, j] 个字符是否匹配(存储的值视问题而定)。
    对string和pattern首部都加上一个空格或者可以认为是正则中的’.’,方便后续操作:
    string :“aaaabb” -> " aaaabb"
    pattern: “bc” -> " bc"
    并且初始化f[i][0] = True,视问题初始化,比如计数问题就是初始化为1,存在问题就记为True。表示pattern头部的空格可以与string任意位置匹配。
    转移方程:
  1. 若要求pattern在string以子串的形式出现
    if string[i] == pattern[j]:
    此时只看string[0,i-1]与pattern[0, j-1]是否匹配
    f[i][j] = f[i-1][j-1]
    else:
    f[i][j] = False
  2. 若要求pattern在string以子序列出现
    f[i][j] = f[i-1][j] or (False if string[i] != pattern[j] else f[i-1][j-1])
    f[i-1][j]:因为子序列可以不连续,即string[i]可以不与pattern[j]匹配
    三元表达式对应string[i]与pattern[j]匹配的情况
    看一道具体的题目:
  3. 输入:s = “rabbbit”, t = “rabbit”
    输出:3
    解释:
    如下图所示, 有 3 种可以从 s 中得到 “rabbit” 的方案。
    (上箭头符号 ^ 表示选取的字母)
    r a b b b i t
    ^ ^ ^ ^ ^^
    r a b b b i t
    ^ ^ ^ ^ ^^
    r a b b b i t
    ^ ^ ^ ^ ^ ^
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/distinct-subsequences
    一道计数的问题,dp[i][j]存储的是s[0, i]与t[0, j]的匹配方案数。
    同样的在s,t的开头加上空格,初始化dp[i][0]=1。
def numDistinct(self, s: str, t: str) -> int:
    s = " "+s
    t = " "+t
    m = len(t)
    n = len(s)
    dp = [[0]*m for _ in range(n)]
    for i in range(n):
        dp[i][0] = 1
    # 因为t, s加了空格所以t[0]与s的任意位置i匹配都是1
    # dp[i][j] s[:i+1]与t[:j+1]的匹配数
    for i in range(1, n):  # 跳过空格
        for j in range(1, m):
            dp[i][j] = dp[i-1][j] + (0 if s[i] != t[j] else dp[i-1][j-1])
            # dp[i-1][j]不用s[i]的方案数
            # 0 if s[i] != t[j] else dp[i-1][j-1] 使用s[i]的方案数
    return dp[-1][-1]  # 返回s末尾与t末尾的匹配的方案数
  1. 给定文本text和待匹配字符串pattern,二者皆只包含小写字母,并且不为空。
    在text中找出匹配pattern的最短字符串,匹配指按序包含pattern,但不要求pattern连续。
    如text为abaacxbcbbbbacc,pattern为cbc,text中满足条件的是abaacxbcbbbbacc下划线部分。
    输出最短匹配序列起止位置(位置下标从0开始),
    用空格分隔。若有多个答案,输出起止位置最小的答案;
    若无满足条件的答案,则起止均为-1。
    拆解问题:首先完成子序列的匹配问题,然后在匹配的方案中找到距离最小的方案输出下标。
def solution(txt, pt):
    dp = {}  # 存储txt[0, i]是否匹配pt[0, j]
    n = len(txt)+1
    m = len(pt)+1
    txt = " "+txt
    pt = " "+pt
    for i in range(n):
        dp[i, 0] = True
    for i in range(1, n):
        for j in range(1, m):
            if txt[i] == pt[j]:
                dp[i, j] = dp.get((i-1, j), False) or dp.get((i-1, j-1), False)
            else:
                dp[i, j] = dp.get((i-1, j), False)
    pt_end = m-1
    min_gap = float('inf')
    res = []
    # 只看j=m-1的是否匹配,在匹配的结尾向前找起点
    for i in range(1, n):  # 不含" "
        if dp[i, pt_end]:  # s[0, i]能匹配到pt结尾
            cur_txt = i  # 现在txt匹配位置的结尾
            cur_pt = pt_end
            while cur_pt > 0: # 向前找txt起点
                if txt[cur_txt] == pt[cur_pt]:
                    cur_txt -= 1
                    cur_pt -= 1
                else:
                    cur_txt -= 1
            if i - cur_txt < min_gap:  # 比较gap的大小
                min_gap = i - cur_txt
                res = [cur_txt, i-1]
    return res if res != [] else [-1, -1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值