回文串是指aba、abba、cccbccc、aaaa这种左右对称的字符串。每个字符串都可以通过向中间添加一些字符,使之变为回文字符串。
例如:abbc 添加2个字符可以变为 acbbca,也可以添加3个变为 abbcbba。方案1只需要添加2个字符,是所有方案中添加字符数量最少的。
输入
输入一个字符串Str,Str的长度 <= 1000。
输出
输出最少添加多少个字符可以使之变为回文字串。
输入样例
abbc
输出样例
2
题解:
题目的意思很简单,就是给定一个字符串,怎么添加最少的字符个数使之变成回文字符串,只是这么光想是想不出来的。
思路1:LCS
将一个字符串str反转之后得到字符串re_str,那么这两个字符串合到一起一定是回文串,比如
str = abbc,re_str = cbba,合到一起的字符串 = abbc cbba,这样就得到了回文字符串,但是其实并不需要添加这么多个长度,str和re_str可能会有公共子序列,比如这里的bb,那么再多加两个bb是不必要的,只要一个c和a即可,所以只要求出str的翻转字符串re_str,然后求两个字符串的LCS(最长公共子序列)即可,最后要添加的字符个数就是str.length() - LCS(str,re_str)
其实这里博主也不是很理解,也是看的别人的。
思路2:区间DP
(如果对区间DP不理解的同学可以去先百度下,其实我用的也很少,但是很好理解)
状态转移方程: 具体去看代码吧,代码中说的比较详细了。
if(str[i] == str[j])
{
dp[i][j] = dp[i+1][j-1];// 若第i个 == 第j个,说明这一对没问题,只要看里面的i+1 --- j-1
if(len == 2)
{
dp[i][j] = 0;// 长度为2的时候,上面的式子dp[i+1][j-1]是没有意义的,长度为2而且两个字符也相等,那添加的字符个数就为0
}
}
else
{
// 如果不相等,则选择i+1 --- j 和 i --- j-1的要添加字符串个数小的那一个,并且因为这里有一个字符不匹配,所以要+1,用来匹配第i个或者第j个
dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1;
}
}
代码1:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
#include <list>
#include <utility>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <bitset>
#include <iterator>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double E = exp(1.0);
const int MOD = 1e9+7;
const int MAX = 1e3+5;
string str;
/* LCS解法 */
int dp[MAX][MAX];
int LCS(string str,string tmp)
{
int len_str = str.length();
int len_tmp = tmp.length();
for(int i = 1; i <= len_str; i++)
{
for(int j = 1; j <= len_tmp; j++)
{
if(str[i-1] == tmp[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
else
{
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
}
}
}
return dp[len_str][len_tmp];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin >> str)
{
/* LCS解法 */
string tmp = str;
reverse(str.begin(),str.end());
cout << str.length() - LCS(tmp,str) << endl;
}
return 0;
}
代码2:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
#include <list>
#include <utility>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <bitset>
#include <iterator>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double E = exp(1.0);
const int MOD = 1e9+7;
const int MAX = 1e3+5;
string str;
/* 区间DP解法 */
int DP(string str)
{
int dp[MAX][MAX];// dp[i][j]表示从字符串的i下标位置 --- j下标位置,形成一个回文串需要添加多少字符
memset(dp,0,sizeof(dp));
int str_len = str.length();
// 字符串长度为0和1的一定都是回文串,所以不需要添加字符,就是默认的0
for(int len = 2; len <= str_len; len++)// 字符串长度从2开始
{
for(int i = 0; i + len - 1 < str_len; i++)// 字符串的起点i
{
int j = i + len - 1;// 字符串的终点j
if(str[i] == str[j])
{
dp[i][j] = dp[i+1][j-1];// 若第i个 == 第j个,说明这一对没问题,只要看里面的i+1 --- j-1 }
if(len == 2)
{
dp[i][j] = 0;// 长度为2的时候,上面的式子dp[i+1][j-1]是没有意义的,长度为2而且两个字符也相等,那添加的字符个数就为0
}
}
else
{
// 如果不相等,则选择i+1 --- j 和 i --- j-1的要添加字符串个数小的那一个,并且因为这里有一个字符不匹配,所以要+1,用来匹配第i个或者第j个
dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1;
}
}
}
return dp[0][str_len - 1];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin >> str)
{
/* 区间DP */
cout << DP(str) << endl;
}
return 0;
}