题解:洛谷 P1435 回文子串
【题目来源】
【题目描述】
回文词是一种对称的字符串。任意给定一个字符串,通过插入若干字符,都可以变成回文词。此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数。
比如 \(Ab3bd\) 插入 \(2\) 个字符后可以变成回文词 \(dAb3bAd\) 或 \(Adb3bdA\),但是插入少于 \(2\) 个的字符无法变成回文词。
注意:此问题区分大小写。
【输入】
输入共一行,一个字符串。
【输出】
有且只有一个整数,即最少插入字符数。
【输入样例】
Ab3bd
【输出样例】
2
【核心思想】
-
问题分析:给定一个字符串,求将其变成回文串所需要插入的最少字符数。这是一个线性DP问题,关键在于利用最长公共子序列(LCS)的思想,将问题转化为求字符串与其反转串的LCS长度。
-
算法选择:
- 最长公共子序列(LCS):将字符串与其反转串求LCS,得到最长回文子序列长度
- 问题转化:最少插入字符数 = 字符串总长度 - 最长回文子序列长度
- 二维DP:
dp[i][j]表示字符串 \(s_1[1..i]\) 和 \(s_2[1..j]\) 的LCS长度
-
关键步骤:
- 构造反转串:将原字符串 \(s\) 复制并反转得到 \(s_2\)
- LCS状态转移:对于 \(i\) 从 \(1\) 到 \(n\),\(j\) 从 \(1\) 到 \(n\)
- 若 \(s_1[i] == s_2[j]\):
dp[i][j] = dp[i-1][j-1] + 1,当前字符匹配,LCS长度加1 - 若 \(s_1[i] \neq s_2[j]\):
dp[i][j] = max(dp[i-1][j], dp[i][j-1]),取上方或左方的最大值
- 若 \(s_1[i] == s_2[j]\):
- 计算答案:
ans = n - dp[n][n],总长度减去最长回文子序列长度即为最少插入字符数
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),双重循环遍历两个字符串
- 空间复杂度:\(O(n^2)\),二维DP数组(可优化为 \(O(n)\))
-
LCS求回文插入最少字符的核心思想:
- 对称性利用:回文串正读反读相同,将字符串与其反转串求LCS,得到的即为最长回文子序列
- 问题转化:不需要直接求最少插入数,而是转化为求最长回文子序列,再用总长度减去它
- 最优子结构:LCS具有最优子结构性质,当前状态由子问题最优解推导
- 空间优化:因为只依赖上一行和当前行左侧,可用滚动数组将空间优化至 \(O(n)\)
- 适用于"求字符串变成回文串的最少插入/删除次数"类问题
【解题思路】

【算法标签】
普及 #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 定义最大字符串长度
string s1, s2; // s1: 原始字符串,s2: 反转后的字符串
int dp[N][N]; // DP数组,用于存储最长公共子序列长度
int main()
{
// 输入原始字符串
cin >> s2;
// 复制原始字符串并反转
string s1 = s2;
reverse(s2.begin(), s2.end());
// 在字符串前添加空格,使索引从1开始
s1 = " " + s1;
s2 = " " + s2;
int len = s1.size(); // 获取字符串长度(包含前导空格)
// 动态规划计算最长公共子序列
for (int i = 1; i <= len; i++)
{
for (int j = 1; j <= len; j++)
{
if (s1[i] == s2[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 << len - dp[len][len];
return 0;
}
【运行结果】
Ab3db
2
浙公网安备 33010602011771号