【数据结构与算法】LCS刷题

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]变成回文的最少插入次数

转移:

  • 相等 → dpi+1j-1

  • 不等 → min(dpi+1j, dpij-1) + 1

但相比之下:

LCS写法更简单、更统一


总结

这道题的关键不是DP,而是"转化":

  • 从"插入多少" → 转化为"保留多少"

  • 从"回文问题" → 转化为"LCS问题"

最后得到:

复制代码
最少插入次数 = n - 最长回文子序列

最后一句

如果你刚学动态规划,这道题非常值得反复体会:

真正难的不是写代码,而是把问题变成你熟悉的模型

当你能做到这一点,很多看似复杂的题,其实都只是"换了个皮的模板题"。

相关推荐
鱼鱼不愚与2 小时前
《原来如此 | 第01期:为什么导航软件能预测红绿灯倒计时?》
算法
复杂网络6 小时前
论最小 Agent 计算机的形态
算法
kisshyshy1 天前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2124 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试