
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
string t;
t=s;
reverse(s.begin(),s.end());
int n=s.size();
vector<vector<int>>dp(n+1,vector<int>(n+1,0));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i-1]==t[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout<<n-dp[n][n]<<endl;
return 0;
}
这道题很多人第一眼会觉得是"回文构造问题",但如果你直接去模拟插入,基本会走进死胡同。真正的解法,其实和一个非常经典的算法------LCS(最长公共子序列)------有着很深的联系。
这篇文章不只是讲解代码,而是带你把这道题的思路彻底理顺。
问题本质;我们到底在求什么
题目要求:
给定一个字符串,可以通过插入字符把它变成回文串,求最少插入次数
例如:
Ab3bd → 最少插入 2 个字符
注意几个关键词:
-
可以插入任意字符
-
插入位置不限
-
目标是变成回文
一个关键转化;问题其实不是"插入",而是"保留"
如果你换个角度想:
与其考虑插入多少字符,不如考虑"原字符串最多能保留多少不动"
为什么?
因为:
-
最终变成回文
-
中间那一部分可以不用动
-
只需要在两边补齐
于是问题就变成:
找出原字符串中最长的"回文子序列"
再进一步;回文子序列怎么求
这里就是这道题的核心转折点:
最长回文子序列 = 原字符串 和 反转字符串 的 LCS
也就是说:
s = 原字符串
t = reverse(s)
那么:
LCS(s, t) = 最长回文子序列长度
为什么这样是对的
回文的本质是:
正着读和反着读一样
而:
-
s 是正序
-
t 是反序
它们的最长公共子序列,恰好就是"可以对称匹配"的部分。
最终结论;一行公式
有了上面的推导,答案就很简单了:
最少插入次数 = n - LCS(s, reverse(s))
代码解析;其实就是LCS
cpp
string s;
cin >> s;
string t = s;
reverse(s.begin(), s.end());
int n = s.size();
vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(s[i-1] == t[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
cout << n - dp[n][n] << endl;
逐步理解这段代码
; 第一步:构造反转字符串
t = 原字符串
s = 反转后的字符串
这样问题就转化为 LCS(s, t)。
; 第二步:定义dp数组
dp[i][j] = s前i个字符 和 t前j个字符 的LCS长度
; 第三步:状态转移
如果字符相等 → dp[i-1][j-1] + 1
否则 → max(dp[i-1][j], dp[i][j-1])
这就是标准的LCS写法。
; 第四步:结果转换
dp[n][n] = 最长回文子序列长度
答案 = n - dp[n][n]
用样例走一遍;更直观一点
s = Ab3bd
t = db3bA
LCS结果是:
b3b(长度3)
所以:
n = 5
答案 = 5 - 3 = 2
为什么"减法"成立
你可以这样理解:
-
LCS部分 → 已经是回文结构
-
其他字符 → 需要补对称
所以:
不在LCS中的字符,都需要通过插入来补齐
常见误区
很多人一开始会:
-
想用双指针贪心 → 不行
-
想直接模拟插入 → 太复杂
-
想用区间DP但没思路 → 卡住
而这道题最巧妙的地方就在于:
它可以优雅地转化为LCS问题
再补一个思路(进阶)
其实这题还有一种经典写法:
区间DP(直接求最少插入)
定义:
dp[i][j] = 区间[i, j]变成回文的最少插入次数
转移:
-
相等 → dp[i+1][j-1]
-
不等 → min(dp[i+1][j], dp[i][j-1]) + 1
但相比之下:
LCS写法更简单、更统一
总结
这道题的关键不是DP,而是"转化":
-
从"插入多少" → 转化为"保留多少"
-
从"回文问题" → 转化为"LCS问题"
最后得到:
最少插入次数 = n - 最长回文子序列
最后一句
如果你刚学动态规划,这道题非常值得反复体会:
真正难的不是写代码,而是把问题变成你熟悉的模型
当你能做到这一点,很多看似复杂的题,其实都只是"换了个皮的模板题"。