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

最后一句

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

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

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

相关推荐
无敌憨憨大王1 小时前
最小生成树
算法
Jasmine_llq1 小时前
《B4258 [GESP202503 一级] 四舍五入》
数据结构·算法·整数运算实现四舍五入整十数算法·批量输入遍历算法·逐行输出算法·整数算术运算组合算法·顺序输入处理算法
2401_874732531 小时前
模板编译期排序算法
开发语言·c++·算法
weixin_421922691 小时前
C++与Node.js集成
开发语言·c++·算法
j_xxx404_2 小时前
力扣--分治(归并排序)算法题I:排序数组,交易逆序对的总数
数据结构·c++·算法·leetcode·排序算法
sprite_雪碧2 小时前
排版类问题(机试高频)
c语言·数据结构·算法
暮冬-  Gentle°2 小时前
设计模式在C++中的实现
开发语言·c++·算法
2501_908329852 小时前
实时音频处理C++实现
开发语言·c++·算法
dapeng28702 小时前
移动语义与完美转发详解
开发语言·c++·算法