【数据结构与算法】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 - 最长回文子序列

最后一句

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

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

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

相关推荐
Jerry1 小时前
LeetCode 101. 对称二叉树
算法
可编程芯片开发2 小时前
基于MPPT最大功率跟踪的离网光伏发电系统Simulink建模与仿真
算法
AI科技星2 小时前
线性算子不是空间映射函数,是全域双螺旋场之间拉伸、旋转、耦合、坍缩的跨空间标准化变换载体《全域数学vs传统数学:人类文明进阶200讲》第80讲
线性代数·算法·矩阵·数据挖掘·回归·乖乖数学·全域数学
米罗篮3 小时前
矩阵快速幂 (Exponentiation By Squaring Applied To Matrices)
c++·线性代数·算法·矩阵
dream_home84073 小时前
图像算法模型NPU适配与算法服务实战指南
人工智能·python·算法·npu 图像服务
大鱼>3 小时前
多宠物家庭智能管理平台:云端架构与多设备协同实战
python·算法·iot·宠物
To_OC4 小时前
LC 22 括号生成:刷完这道题,我终于搞懂回溯剪枝了
javascript·算法·leetcode
To_OC4 小时前
LC 39 组合总和:回溯入门必刷题,我踩过的两个坑都在这了
javascript·算法·leetcode
数字杂技师4 小时前
每条推荐都很准,为什么我还是越刷越无聊?
算法
兰令水4 小时前
hot100【acm版】【2026.7.14打卡-java版本】
java·数据结构·算法·leetcode·面试