栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

1312. 让字符串成为回文串的最少插入次数

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

1312. 让字符串成为回文串的最少插入次数

1143. 最长公共子序列

583. 两个字符串的删除操作

方法一:动态规划

最长公共子序列:Longest Common Subsequence 简称 LCS

状态定义: dp[i][j] 表示 text1[:i] 与 text2[:j] 的 LCS 的长度

初始化: dp = [[0] * (n + 1) for _ in range(m + 1)]
注意 行 m, 列 n,dp[0][0] 表示 text1 = text2 = “”。

转移方程:
d p [ i + 1 ] [ j + 1 ] = { d p [ i ] [ j ] + 1 text1[i] == text2[j] m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] ) text1[i] != text2[j] dp[i+1][j+1]= begin{cases} dp[i][j]+1 & text{text1[i] == text2[j]}\ max(dp[i + 1][j], dp[i][j +1]) & text{text1[i] != text2[j]} end{cases} dp[i+1][j+1]={dp[i][j]+1max(dp[i+1][j],dp[i][j+1])​text1[i] == text2[j]text1[i] != text2[j]​

为了避免判断 i - 1,j - 1 合法性,直接定义 dp 数组 (n+1) * (m+1)。注意 dp 与 text 的索引

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        dp = [[0] * (n + 1) for _ in range(m + 1)] # 行 m, 列 n
        
        for i in range(m):
            for j in range(n):
                if text1[i] == text2[j]:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i+1][j],dp[i][j+1])

        return dp[m][n]
二维数组到一维数组的优化

对动态规划进行了优化,因为只涉及到两行,分别定义两个变量记录。

jj + 1
a……a[j]a[j+1]
b……b[j]b[j+1]
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int: 
        m, n = len(text1), len(text2)
        
        if text1 in text2: return m
        if text2 in text1: return n 
       
        a, b = [0] * (n + 1),  [0] * (n + 1)

        for i in range(m):
            for j in range(n):
                if text1[i] == text2[j]: b[j+1] = a[j] + 1
                else: b[j+1] = max(b[j],a[j+1])
                    
            a, b = b, a                
        
        return a[n]
二维数组到一维数组的优化

对动态规划进行了优化,因为只涉及到两行,用到第一行与第二行前面数据,所以可以优化到一维数组。

比如计算 dp[2][2] 的时候, 可能用到 dp[1][1], dp[1][2], 和dp[2][1]。 左侧数据在同一行,上方数据还未被覆盖,注意左上方的数据,因为在计算前一列的时候可能会被覆盖,需要保存。

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int: 
        m, n = len(text1), len(text2)        
        if text1 in text2: return m
        if text2 in text1: return n        
        dp = [0] * (n + 1)

        for i in range(m):
            upLeft = dp[0] # 每一行的开始都是 0 
            for j in range(n):
                tmp = dp[j + 1] # 缓存 dp[j + 1], 后面会被覆盖。
                if text1[i] == text2[j]: 
                    dp[j+1] = upLeft + 1
                else: 
                    dp[j+1] = max(dp[j],dp[j+1])
                    
                upLeft = tmp  # 下一次作为 dp[i][j] 用           
        
        return dp[n]
516. 最长回文子序列 方法一:递归 + 双指针

取 s 的首尾两字母 x 和 y,如果 x == y,除去 x、y 的最长回文子序列的长度 + 2,如果 x != y 除去 x 或 y 的最大的回文子序列长度中的最大值。
如: “bbbab”,“bba” 最大的回文子序列的长度 + 2,“ba” 和 “bb” 最大值是 2。

class Solution:
    @lru_cache(None)
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        if n < 2: return n
        l, r = 0, len(s)-1
        while l < r:
            if s[l] == s[r]:
                return self.longestPalindromeSubseq(s[l+1:r]) + 2
            else:
                return max(self.longestPalindromeSubseq(s[l+1:r+1]), self.longestPalindromeSubseq(s[l:r]))
方法二:递归

对 s 中的每一个字母都要遍历,取最大值,超时。

def longestPalindromeSubseq(self, s: str) -> int:
	n = len(s)
    if n < 2: return n
    res = 0
    for i, c in enumerate(s):        
        r = str.rindex(s, c)        
        if r > i:
            res = max(res, self.longestPalindromeSubseq(s[i+1:r]) + 2)
        else:
            res = max(res, 1)
    return res
方法三:递归 + 二分
class Solution:      
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        if n < 2: return n
        cIndex = defaultdict(list)
        for i, c in enumerate(s): cIndex[ord(c) - ord('a')].append(i)

        @lru_cache(None)
        def dfs(l, r):
            if l >= r: return 1 if l == r else 0
            ans = 0
            
            for i in range(26):
                left = bisect.bisect_left(cIndex[i], l)

                if left == len(cIndex[i]): continue
                right = bisect.bisect_left(cIndex[i], r)
                print(right,)
                if right == len(cIndex[i]) or cIndex[i][right] > r:
                    right -= 1
                ans = max(ans, dfs(cIndex[i][left]+1, cIndex[i][right]-1) + 2) if right > left else max(ans, 1)
            return ans

        return dfs(0, n-1)
方法四:动态规划

dp[i][j] 表示字符串 s 的下标区间 [i, j] 内的最长回文子序列的长度。

dp = [[0] * n for _ in range(n)] # n = len(s)
dp[i][i] = 1 # 表示单个字符的回文长度为1

d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] + 2 s[i] == s[j] m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) s[i] != s[j] dp[i][j]= begin{cases} dp[i+1][j−1]+2& text{s[i] == s[j]}\ max(dp[i+1][j], dp[i][j−1])& text{s[i] != s[j]} end{cases} dp[i][j]={dp[i+1][j−1]+2max(dp[i+1][j],dp[i][j−1])​s[i] == s[j]s[i] != s[j]​

遍历方向:从下往上进行遍历

返回 dp[0][−1]

class Solution:
    def minInsertions(self, s: str) -> int:
        n = len(s)
        # dp = [[1 if i == j else 0 for i in range(n)] for j in range(n)] 
        dp = [[0] * n for _ in range(n)]
        for i in range(n-1,-1,-1):
            dp[i][i] = 1            
            for j in range(i+1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])
                    
        return n - dp[0][-1]
1312. 让字符串成为回文串的最少插入次数 方法一:动态规划

本题属于 516. 最长回文子序列 的子题,只需要用字符串长度减去最长回文子序列的长度就是最少插入次数。

注意:需要从后往前遍历,因为每一个 d p [ i ] [ j ] dp[i][j] dp[i][j] 取决于它前面的最大回文子序列长度。

class Solution:
    def minInsertions(self, s: str) -> int:
        n = len(s)
        # dp = [[1 if i == j else 0 for i in range(n)] for j in range(n)] 
        dp = [[0] * n for _ in range(n)]
        for i in range(n-1,-1,-1):
            dp[i][i] = 1            
            for j in range(i+1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])
                    
        return n - dp[0][-1]

当在字符串 s 中找到最长回文子序列后,对于在 s 中但不在子序列中的那些字符,如果其在回文中心的左侧,就在右侧对应的位置添加一个相同的字符;如果其在回文中心的右侧,就在左侧对应的位置添加一个相同的字符。

那么如何求出 s 的最长回文子序列 sPA 呢?sPA 就等同于 s 和 s[::-1] 的最长公共子序列。
参考:1143. 最长公共子序列

class Solution:
    def minInsertions(self, s: str) -> int:
        n = len(s)
        # t = s[::-1]
        dp = [[0] * (n + 1) for _ in range(n + 1)]
        
        for i in range(n):
            for j in range(n):
                # if s[i] == t[j]:
                if s[i] == s[-j-1]:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])

        return n - dp[-1][-1]
方法二:区间动态规划

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示对于字符串 s 的子串 s [ i : j + 1 ] s[i:j+1] s[i:j+1],最少添加的字符数量,使得 s [ i : j + 1 ] s[i:j+1] s[i:j+1] 变为回文串。

求 d p [ i ] [ i + k ] dp[i][i+k] dp[i][i+k],也就是 s [ i : i + k + 1 ] s[i:i+k+1] s[i:i+k+1] 变成回文字符串的最少插入次数。

class Solution:
    def minInsertions(self, s: str) -> int:
        n = len(s)   
        dp = [[0] * n for _ in range(n)]

        for k in range(1,n):
            for i in range(n-k):
                if s[i] == s[i+k]:
                    dp[i][i+k] = dp[i+1][i+k-1]
                else:
                    dp[i][i+k] = min(dp[i+1][i+k] + 1, dp[i][i+k-1] + 1)
        return dp[0][-1]
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/275424.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号