让字符串成为回文串的最少插入次数
题目
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
示例 1:
输入:s = “zzazz”
输出:0
解释:字符串 “zzazz” 已经是回文串了,所以不需要做任何插入操作。
示例 2:
输入:s = “mbadm”
输出:2
解释:字符串可变为 “mbdadbm” 或者 “mdbabdm” 。
示例 3:
输入:s = “leetcode”
输出:5
解释:插入 5 个字符后字符串变为 “leetcodocteel” 。
提示:
1 <= s.length <= 500
s 中所有字符都是小写字母。
解题思路:
这种关于字符串的最长序列、最少修改次数等问题,大概率逃不过动态规划。
很朴素的一个思路是,我们创造一个数组dp[i][j],用来存储字符串s[i:j]成为回文串的最少插入次数。根据动态规划的思想,需要建立状态转移方程。我们通过题意可以得出以下结论:
1.如果 s[i] == s[j],那么最外层已经形成了回文,我们只需要继续考虑 s[i+1:j-1];
2.如果 s[i] != s[j],那么我们要么在 s[i:j] 的末尾添加字符 s[i],要么在 s[i:j] 的开头添加字符 s[j],才能使得最外层形成回文。如果我们选择前者,那么需要继续考虑 s[i+1:j];如果我们选择后者,那么需要继续考虑 s[i:j-1]。
这个题目之所以会成为一个难度系数是困难的题目,我个人觉得其难点在于二维dp的遍历和初始化,如果我们按照正常思路进行遍历dp的话,会发现dp[i][j] 的取值与dp[i + 1][j], dp[i][j - 1],dp[i + 1][j - 1]有关,但是如果选择从下往上遍历,则第一步所求就是题目的要求答案,简单来说,就是想用动归求解A的答案,但是动归的初始化就是A的答案。陷入了死循环。因此本题目的难点在于怎么遍历二维dp。
一种可行的方法是:
递增地枚举子串 s[i:j] 的长度 span = j - i + 1,再枚举起始位置 i,通过 j = i + span - 1 得到 j 的值并计算 dp[i][j]。这样的计算顺序可以保证在计算 dp[i][j] 时,状态转移方程中的状态 dp[i + 1][j],dp[i][j - 1] 和 dp[i + 1][j - 1] 均已计算过。
class Solution:
def minInsertions(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for span in range(2, n + 1):
for i in range(n - span + 1):
j = i + span - 1
dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1
if s[i] == s[j]:
dp[i][j] = min(dp[i][j], dp[i + 1][j - 1])
return dp[0][n - 1]