本文是观看了很多博客,最后整理而成……
- 回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
手动判断该字符串是否为回文串:
#include <iostream>
using namespace std;
int main(){
string str;
int i, j;
while (cin >> str){
int flag = 1;
for (i = 0,j = str.length()-1; i <= j; i++,j--){
if (str[i] != str[j]){
flag = 0;
break;
}
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
利用函数判断给字符串是否为回文串:
#include<iostream>
#include<algorithm>
using namespace std;
int hui(string s){
string s1 = s;
reverse(s.begin(), s.end());
return s==s1;
}
int main(){
string s;
cin >> s;
if(hui(s)) cout << "yes";
else cout << "no";
return 0;
}
- 回文子串:在原串中连续出现的字符串片段
找出最长的回文子串
方法一: 暴力求解
把该字符串的所有子串列举出来,分别判断子串是否为回文串,找出最长的那个子串;
列举所有的回文子串时间复杂度为O(n^2),判断是否为回文时间复杂度为O(n),两者循环相套,故最终复杂度为 O(n^3),此方法时间复杂度过大,容易超时,不建议使用;
#include<iostream>
#include<algorithm>
using namespace std;
int hui(string s){
string s1 = s;
reverse(s.begin(), s.end());
return s1 == s? 1 : 0;
}
int main(){
string s, m;
int t;
while(!(cin >> s).eof()){
int maxn = 0;
for(int i = 0; i < s.length(); i++){
for(int j = 1; j+i <= s.length(); j++){
m = s.substr(i, j);
if(hui(m)){
t = m.length();
if(maxn < t){
maxn = t;
}
}
}
}
cout << maxn << endl;
}
return 0;
}
方法二:动态规划法
令 dp[i][j] 表示 S[i] 至 S[j] 所表示的子串是否为回文子串,是则为1,不是为0。如此根据 S[i] 是否等于 S[j],可以把问题分为两类:
(1)S[i] == S[j],那么只要 S[i+1] 至 S[j-1] 是回文子串,S[i] 至 S[j] 就是回文子串;如果 S[i+1] 至 S[j-1] 是不是回文子串,则 S[i] 至 S[j]也不是回文子串。
(2) S[i] != S[j],那么 S[i] 至 S[j] 一定不是回文子串。
由此可以写出其状态转移方程:
边界:dp[i][i] = 1,dp[i][i+1] = (S[i]==S[i+1] ? 0 : 1) ps:这里的dp初始化记录了长度为1和2的回文子串
但是这里还存在一个问题,就是在求 dp[i][j] 时,无法保证 dp[i+1][j-1] 已经被计算了,比如先固定 i=0,然后 j 从 2 开始枚举。当求解dp[0][2] 时,dp[1][1]已经在初始中得到;当求解dp[0][3]时,会转换求dp[1][2],而dp[1][2]也在初始化中获得;当求解dp[0][4]是,转换求解dp[1][3],但dp[1][3]之前却没有被计算出来,因此无法转移状态。
根据上面的公式,边界的长度表示长度为1和2的回文子串,且每次转移时都说对子串的长度减1。因此不妨按照子串的长度和子串的初始位置进行枚举,即第一次可以枚举长度为3的子串的dp值,第二次在第一次的基础上枚举长度为4的子串的dp值....直到枚举到原字符串的长度。
时间复杂度为 O(n^2);
根据分析,写出如下代码:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int M=1010;
int dp[M][M];
int main(){
int i,j,k,s,e,ans=1;
string str;
cin>>str;
int len=str.length();
for(i=0;i<len;i++){
dp[i][i]=1;
if(str[i]==str[i+1]){
dp[i][i+1]=1;
ans=2;
}
}
for(k=3;k<=len;k++){ //子串的长度
for(i=0;i+k-1<len;i++){ //子串左端点
j=i+k-1; //子串右端点
if(str[i]==str[j]&&dp[i+1][j-1]){
dp[i][j]=1;
ans=k;
s=i;e=j; //保存最长回文子串的下标
}
}
}
for(i=s;i<=e;i++){
cout<<str[i];
}
cout<<endl<<ans<<endl;
}
方法三:中心扩展
回文串其实就是关于中心对称的,回文中心的两侧互为镜像,因此,回文可以从它的中心展开,并且只有 2n-1 个这样的中心;
解释: 2n - 1
(1)当回文的中心为双数时:如 abba ,可以划分为 ab bb ba,对于n长度的字符串,这样的划分有 n-1 种。
(2)当回文的中心为单数时,如 abcd ,可以划分为 a b c d, 对于n长度的字符串,这样的划分有 n 种。
比如有字符串 aaabcbabb,这时最长回文子串是 abcba,中心是c;又有字符串aaadccdabcd,这时最长回文子串是 adccda,中心是cc。 由此可见中心点既有可能是一个字符,也有可能是两个字符,当中心为一个字符的时候有 n 个中心,当中心为两个字符的时候有 n-1 个中心,所以一共有 2n-1 个中心。 然后for循环开始从左到右遍历,为什么会有两次 expandAroundCenter,一次是 i 和 i 本身,一次是 i 和 i+1,这就是上面说到的一个中心与两个中心。 而后会去判断这两种情况下谁的回文子串最长,并标记出这个子串在原字符串中的定位,即 start 和 end。
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L - 1;
}
下面代码是先找一个中心的回文串,再找两个中心的回文串;
#include<iostream>
#include<cstring>
using namespace std;
string longest(string &s){
const int len = s.size();
int maxlen = 1;
int start = 0;
for(int i = 0; i < len; i++){//求长度为奇数的回文串
int j = i - 1, k = i + 1;
while(j >= 0 && k < len && s.at(j) == s.at(k)){
if(k - j + 1 > maxlen){
maxlen = k - j + 1;
start = j;
}
j--;
k++;
}
}
for(int i = 0; i < len; i++){//求长度为偶数的回文串
int j = i, k = i + 1;
while(j >= 0 && k < len && s.at(j) == s.at(k)){
if(k - j + 1 > maxlen)
{
maxlen = k - j + 1;
start = j;
}
j--;
k++;
}
}
return s.substr(start, maxlen);
}
int main()
{
string s;
cin >> s;
int n = longest(s).size();
cout << longest(s) << endl;
cout << n;
return 0;
}
方法四:Manacher 法(俗称马拉车算法)
马拉车算法Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,即 O(n),这是非常了不起的。
【Manacher算法原理及实现过程】
为啥会有这个马拉车算法,我们知道回文子串的判定和长度的奇偶性是有关系的,由于回文分为偶回文(比如 bccb)和奇回文(比如 bcacb),而在处理奇偶问题上会比较繁琐,所以这里我们使用一个技巧,在字符间插入一个字符(前提这个字符未出现在串里),常用的是"$""#"。
由于回文串的长度可奇可偶,比如 "bob" 是奇数形式的回文,"noon" 就是偶数形式的回文,马拉车算法的第一步是预处理,做法是在每一个字符的左右都加上一个特殊字符,比如加上'#',那么
bob --> #b#o#b#
noon --> #n#o#o#n#
这样做的好处是不论原字符串是奇数还是偶数个,处理之后得到的字符串的个数都是奇数个,这样就不用分情况讨论了,而可以一起搞定。
Len数组的意义及性质
接下来我们还需要和处理后的字符串 s_new[i] 等长的数组 Len,其中 Len[i] 表示以 s_new[i] 字符为中心的最长回文字串的最右字符到s_new[i]的长度(可以看成是回文子串的半径,最右边到中心点的距离),假设最右的元素下标为r,那么Len[i]=r-i+1。若 s_new[i] = 1,则该回文子串就是 s_new[i] 本身,那么我们来看一个简单的例子:
(1)# 1 # 2 # 2 # 1 # 2 # 2 #
1 2 1 2 5 2 1 6 1 2 3 2 1
(2)
Len数组有一个性质,那就是 Len[i]-1 就是该回文子串在原字符串 s 中的长度。
证明:
看上面那个例子,以中间的 '1' 为中心的回文子串 "#2#2#1#2#2#" 的半径是6,而未添加#号的回文子串为 "22122",长度是5,为半径减1。这是个普遍的规律么?我们再看看之前的那个 "#b#o#b#",我们很容易看出来以中间的 'o' 为中心的回文串的半径是4,而 "bob"的长度是3,符合规律。再来看偶数个的情况"noon",添加井号后的回文串为 "#n#o#o#n#",以最中间的 '#' 为中心的回文串的半径是5,而 "noon" 的长度是4,完美符合规律。所以我们只要找到了最大的半径,就知道最长的回文子串的字符个数了。只知道长度无法确定子串,我们还需要知道子串的起始位置。
首先在转换得到的字符串str中,所有的回文字串的长度都为奇数,那么对于以s_new[i]为中心的最长回文字串,其长度就为2*Len[i]-1,经过观察可知,s_new中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。
有了这个性质之后,那么最长回文子串问题就转化为求所有的 Len[i] 的最大值问题
我们还是先来看中间的 '1' 在字符串 "#1#2#2#1#2#2#" 中的位置是7,而半径是6,貌似7-6=1,刚好就是回文子串 "22122" 在原串 "122122" 中的起始位置1。那么我们再来验证下 "bob","o" 在 "#b#o#b#" 中的位置是3,但是半径是4,这一减成负的了,肯定不对。所以我们应该至少把中心位置向后移动一位,才能为0啊,那么我们就需要在前面增加一个字符,这个字符不能是#号,也不能是s中可能出现的字符,所以我们暂且就用美元号 $ 吧。这样都不相同的话就不会改变p值了,那么末尾要不要对应的也添加呢,其实不用的,不用加的原因是字符串的结尾标识为'\0',等于默认加过了。那此时 "o" 在 "$#b#o#b#" 中的位置是4,半径是4,一减就是0了,貌似没啥问题。我们再来验证一下那个数字串,中间的 '1' 在字符串 "$#1#2#2#1#2#2#" 中的位置是8,而半径是6,这一减就是2了,而我们需要的1,所以我们要除以2。之前的 "bob" 因为相减已经是0了,除以2还是0,没有问题。再来验证一下 "noon",中间的 '#' 在字符串 "$#n#o#o#n#" 中的位置是5,半径也是5,相减并除以2还是0,完美。可以任意试试其他的例子,都是符合这个规律的,最长子串的长度是半径减1,起始位置是中间位置减去半径再除以2。
那么下面我们就来看如何求Len数组,需要新增两个辅助变量 mx 和 id,其中 id 为能延伸到最右端的位置的那个回文子串的中心点位置,mx 是回文串能延伸到的最右端的位置,这个算法的最核心的一行如下:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图:
当 P[j] >= mx - i 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] = mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了。
对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
int n,len,len_new;
string s,s_new;
int Len[10000] = {0};
void init(string s) {
s_new.resize(2*len+5);
s_new[0] = '#';
for(int i = 1; i <= len; i++) {
s_new[2*i-1] = s[i-1];
s_new[2*i] = '#';
}
s_new[2*len] = '#';
}
int manacher() {
int ans = -1;
int id=0,mx=0;
Len[0] = 1;
Len[len_new-1] = 1;
for(int i = 1; i < len_new-1; i++) {
int j = 2*id-i;
if(s_new[i] + Len[j] < mx)
Len[j] = min(mx-1,Len[j]);
else
Len[i] = 1;
while(s_new[i - Len[i]] == s_new[i + Len[i]]) Len[i]++;
if(Len[i] + i > mx) {
mx = Len[i]+i;
id = i;
}
}
for(int i = 0; i < len_new; i++)
ans = max(ans,Len[i]-1);
return ans;
}
int main() {
cin>>s;
len = s.length();
init(s);
len_new = 2*len+1;
cout<<"s_new = "<<s_new;
cout<<"\nres = "<<manacher()<<"\n";
for(int i = 0 ; i < len_new; i++) {
cout<<s_new[i]<<" ";
}
cout<<"\n";
for(int i = 0 ; i < len_new; i++) {
cout<<Len[i]<<" ";
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int maxn =1e6;
string str;
string s_new;
int len[maxn<<1];
int init(string st)
{
int len = st.size();
s_new='$';
for(int i =1; i <= 2*len; i+=2)
{
s_new += '#';
s_new += st[i/2];
}
s_new+='#';
s_new+='\0';
return 2*len+1;// 返回 s_new 的长度
}
int Manacher(string st,int len_)
{
int mx = 0,ans = 0,po =0;//mx即为当前计算回文串最右边字符的最大值
for(int i =1; i <= len_ ; i++)
{
if(mx>i)
len[i]=min(mx-i,len[2*po-i]);
else
len[i]=1;//如果i>=mx,要从头开始匹配
while(st[i-len[i]]==st[i+len[i]])
len[i]++;
if(len[i]+i>mx)//若新计算的回文串右端点位置大于mx,要更新po和mx的值
{
mx = len[i]+i;
po = i;
}
ans = max(ans,len[i]);//返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
return ans - 1;
}
int main(){
string s;
cin >> s;
cout << Manacher(s_new, init(s));
return 0;
}
来源:CSDN
作者:heyley..
链接:https://blog.csdn.net/weixin_43850619/article/details/103452408