【数据结构与算法】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]变成回文的最少插入次数

转移:

  • 相等 → dp[i+1][j-1]

  • 不等 → min(dp[i+1][j], dp[i][j-1]) + 1

但相比之下:

LCS写法更简单、更统一


总结

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

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

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

最后得到:

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

最后一句

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

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

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

相关推荐
zyq99101_16 分钟前
DFS算法实战:经典例题代码解析
python·算法·蓝桥杯·深度优先
智者知已应修善业11 分钟前
【51单片机单按键切换广告屏】2023-5-17
c++·经验分享·笔记·算法·51单片机
广州灵眸科技有限公司14 分钟前
为RK3588注入澎湃算力:RK1820 AI加速卡完整适配与评测指南
linux·网络·人工智能·物联网·算法
qinian_ztc21 分钟前
frida 14.2.18 安装报错解决
算法·leetcode·职场和发展
AI应用实战 | RE26 分钟前
012、检索器(Retrievers)核心:从向量库中智能查找信息
人工智能·算法·机器学习·langchain
凤年徐28 分钟前
C++手撕红黑树:从0到200行,拿下STL map底层核心
c++·后端·算法
Thomas.Sir31 分钟前
AI 医疗之罕见病/疑难病辅助诊断系统从算法到实现【表型驱动与知识图谱推理】
人工智能·算法·ai·知识图谱
tankeven1 小时前
动态规划专题(03):区间动态规划从原理到实践(未完待续)
c++·算法·动态规划
田梓燊2 小时前
2026/4/11 leetcode 3741
数据结构·算法·leetcode
斯内科2 小时前
FFT快速傅里叶变换
算法·fft