最长回文字串 (The longest palindrome substring)

这两天去学了一下,觉得下面那篇文章写的很好,有例子,比较容易懂,所以转一下。



以下内容来自:hihoCoder:

小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

小Ho奇怪的问道:“什么叫做最长回文子串呢?”

小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”

良久,小Ho仍然没有头绪,于是只能向小Hi求助。

小Hi清了清嗓子,缓缓说道:“让我从简单的说起吧,我给你一个字符串,你能不能告诉我它是不是一个回文串呢?

小Ho回答道:“这个我当然可以啦!只要将这个字符串反过来,然后比较和原来的字符串是不是一样的不就行了?”

小Hi追问道:“也就是说你想要新建一个字符串咯?”

小Ho道:“那是当然,不然怎么比较呢?”

小Hi笑道:“但是你有没有注意到你在比较原来的字符串A和新字符串B的时候,A的第一个字符就是B的最后一个字符,而A的最后一个字符就是B的第一个字符,那么这样就比较了两次是不是浪费了效率呢?”

小Ho恍然道:“似乎是这样的!我知道了,我也不需要新建一个字符串了,我只需要比较A的第一个字符和最后一个字符是否相同,第二个字符和倒数第二个字符是否相同,以此类推,这样就只要比较字符串长度的一半次数就行了是不是?”

小Hi回答道:“没错!那你对于一个字符串,一一枚举它的子串,然后判断这个子串是不是回文子串,如果是的话就更新当前保存的最长的那一个,是不是就可以了?

小Ho开心道:“是的!这个问题是不是就这么解决了?”

小Hi叹息道:“NONONO!你这最多也就拿个60分吧。”

小Ho遗憾的说道:“才及格啊,那我要怎么多拿点分呢?”

小Hi道:“不急不急,待我慢慢道来,你有没有想过之前的解法有没有什么问题?”

小Ho问道:“有什么问题?”

小Hi道:“你想想,如果一个字符串的[3, 7]这一段已经不是回文子串了,[2, 8]这一段还有可能是回文子串么?

小Ho惊道:“好像不可能,那我之前不是有很多的计算都白费了,有没有什么办法来解决这个问题呢?我得好好想想!”言罢,小Ho沉思了起来。

良久,代表着成功的微笑出现来的小Ho的嘴边:“我知道了!我在枚举子串的时候换一种方式来进行枚举,不是枚举它的起止位置而是尝试枚举子串的中心位置,然后再从小到大依次枚举这个子串的长度,一旦发现已经不是一个回文串了就继续尝试下一个中心位置,这样,似乎就能够避免掉很多问题呢!”

小Hi赞许的点了点头,说道:“没错,这样的确会在一些情况下降低用于计算的时间呢,但是一个全是a的字符串,你这样的枚举方法似乎也没有多大用处呢?不过这样你也能拿个80分了哦!”

小Ho点了点头,说道:“没错,在最坏情况下,这种方法并没有比之前的方法好到哪里去,但是我的直觉告诉我肯定有更加高效的方法来进行计算呢,让我再好好想想吧!。”

小Ho这一想就是三天,小Hi也是看不下去了,决定来开导开导小Ho:“小Ho,你有没有想过,在之前的计算中,计算出以每一个位置为中心的最长回文子串的长度有没有什么用呢?”

小Ho答道:“我想想,如果以第5个字符为中心的最长回文子串的长度是5的话,这就告诉了我[3, 7]这一段是一个回文子串,所以呢?”

小Hi继续提示道:“假设这时候你想要计算以第6个字符为中心的最长回文子串的长度,你有没有什么已知的信息了?”

小Ho边想边说道:“唔,首先第6个字符和第4个字符是一样的,第7个字符和第3个字符是一样的,而第5个字符本身就肯定和第5个字符一样,那么如果[3, 5]这一段是回文子串的话,那么[5, 7]这一段肯定也是回文子串。也就是说,如果f[i] 表示以第i个字符为中心的最长回文子串的长度,我们就会有f[i] >= f[i–2]?”

“不对,还要考虑到f[i – 1]的值,如果f[i – 1]太小就没有意义了,应该是f(i)≥min?{f(i-2), f(i-1)-2}。”小Ho接着补充道。

“没错,但是还有一个问题,如果此时我告诉你f(5) = 1,但是f(4) = 7, f(2) = 3呢?”小Hi追问道。

小Ho想了想,回答道:“理论上来说,我可以通过这些信息知道f(6)>=3,但是由于f(5)=1所以我只能计算出来f(6)>=-1我知道了,我不应该是通过f(i – 1)来辅助计算,而是通过使得右边界(j + f(j) / 2)最大的那个j来辅助计算才是,所以公式将变成 f(i) ≥ min{f(2*j-i) , f(j) -2*(i-j)}这种形式了!”

小Hi继续问道:“那知道了这个公式之后,你打算怎么做呢?”

小Ho想也没想便道:“这简单,我只要在之前枚举中心位置那种方法的基础上,统计使得回文串右边界(j + f(j) / 2)最大的那个j,然后再计算每一个i的时候,都可以通过f(i)≥min?{f(2*j-i), f(j)-2*(i-j)}这个公式来知道f(i)的一个最小值,这样即使是在我们所提到的那种最坏情况下,也可以节省掉很多不必要的计算呢~

一晃就是一周过去了,小Hi还是没有看到小Ho写的程序,于是决定上门去问问。到了小Ho家,小Hi惊讶的发现小Ho对着电脑屏幕,一脸郁闷的样子,于是他走上前问道:“小Ho小Ho,你怎么了啊?”

小Ho一点精神也没有的回答道:“就是上周的那个回文子串的程序啊,我写的时候发现我们当时考虑的解决方法只能处理长度为奇数的回文子串,长度为偶数的回文子串似乎要进行一点点细微的修改,但是这样修改过后就不能用我们最后写出的那个公式来互相帮助进行运算了,要进行很复杂的讨论,我就是一直在想有没有很优美的方法能来解决这个问题。”

小Hi惊讶道:“你就想这个想了一周么?我猜你一定是绕进了分类讨论的这个胡同里走不出来了,为什么不想想有没有别的解决方法呢?”

小Ho问道:“还有什么解决方法呀?”

小Hi答道:“既然长度为偶数的回文子串不好处理,我们为什么不去掉这些回文子串,只处理长度为奇数的回文子串呢?”

小Ho叹息道:“但是长度为偶数的回文子串也可以是答案啊!”

“除非……”小Hi插嘴道。

“除非什么?”小H问道。

“你将所有的长度为偶数的回文子串都变成长度为奇数的回文子串啊,你想想之所以是为偶数,那是因为没有一个中心字符,但是如果我们在原来字符串的基础上,在任意两个相邻的字符间都插入一个特殊字符,是不是无论是原来字符串中长度为奇数的回文子串还是长度为偶数的回文子串,在新的字符串中都有一个长度为奇数的回文子串与之进行对应呢?”

“哦!我了解了,这样我只需要对新的字符串按照我们之前的算法进行计算,统计出的最长回文子串将那些特殊字符去掉之后,就是原来字符串里的最长回文子串了。”小Ho开心的笑道,一连几天的郁闷也是一扫而空。

“但是要注意哦,我们希望得到的最长是原来字符串里的最长,而不是新字符串里的最长,毕竟特殊字符的个数会因为中心字符是不是特殊字符而有差别的哦。”小Hi提醒道。

“恩恩,这样我总能拿到满分了吧?”

“没错呢!”


下面贴一个很水的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <string>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <time.h>

using namespace std;

const int INF = 1<<30;
const int MAXN = (int) 2e6+50;

char str1[MAXN], str2[MAXN];
int cnt[MAXN];
int len1, len2;

void LPS() {
    int j = 0;
    memset(cnt, 0, sizeof(cnt));
    for (int i = 1; i < len2; i++) {
        if (cnt[j]+j>i) cnt[i] = min(cnt[2*j-i], cnt[j]-(i-j));
        else cnt[i] = 1;
        for (cnt[i]; str2[i-cnt[i]]==str2[i+cnt[i]]; cnt[i]++) ;
        if (i+cnt[i] > j+cnt[j]) j = i;
    }
}

int main() {
    #ifdef Phantom01
        freopen("LPS.txt", "r", stdin);
    #endif //Phantom01

    int T; scanf("%d", &T);
    while (T--) {
        scanf("%s", str1);
        len1 = strlen(str1);
        len2 = 0;
        str2[len2++] = ‘$‘;
        str2[len2++] = ‘#‘;
        for (int i = 0; i < len1; i++) {
            str2[len2++] = str1[i];
            str2[len2++] = ‘#‘;
        }
        str2[len2] = ‘\0‘;
        LPS();
        int ans = 0;
        for (int i = 0; i < len2; i++) {
            ans = max(ans, cnt[i]);
        }
        printf("%d\n", (ans-1));
    }

    return 0;
}

最长回文字串 (The longest palindrome substring)

时间: 2024-08-26 12:34:17

最长回文字串 (The longest palindrome substring)的相关文章

最长回文字串理解(学习Manacher&#39;s algorithm)

关于字符串的子串问题,我们经常需要利用的是已经访问的部分的信息,来降低复杂度,和提高效率:在求最长回文子串的问题中,Manacher's algorithm提供了一种很好的机制,虽然部分地方不太容易理解 先说下核心的思想:先对原字符串进行预处理,将字符串"abc"转换为"$#a#b#c#"的形式,既避免了访问越界的问题,又保证每一个字符有至少一个对称的串(真字符,或是假的“#”) 预留两对变量(当前得到的中心位置“center”,和字符串右侧最远位置“R”,以及对称

Hdu 3068 最长回文字串Manacher算法

题目链接 最长回文 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 7976    Accepted Submission(s): 2735 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.回文就是正反读都是一样的字符串,如aba, abba等 Input 输入

最长回文字串

区别于最长公共字串,下面是最长公共字串的算法,利用DP(动态规划): void LCS_dp(char * X, int xlen, char * Y, int ylen) { maxlen = maxindex = 0; for(int i = 0; i < xlen; ++i) { for(int j = 0; j < ylen; ++j) { if(X[i] == Y[j]) { if(i && j) { dp[i][j] = dp[i-1][j-1] + 1; } if

最长回文字串(hdu 3068)

原题链接http://acm.hdu.edu.cn/showproblem.php?pid=3068 查找字符串中最长的回文串,我们用到manachar算法. 要实现manachar算法我们有有两步要做 1:对字符串进行处理,把所有的字符串的长度统一化为奇数.. 1 int l=0; 2 int ans=0; 3 Ma[l++]='$'; 4 Ma[l++]='#'; 5 for(int i=0;i<len;i++) 6 { 7 Ma[l++]=s[i]; 8 Ma[l++]='#'; 9 }

求最长回文字串

题目: 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例 1: 输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案. 示例 2: 输入: "cbbd" 输出: "bb" 知道题网上有很多种解法,比如Manacher算法就是专门解决知道题的,还有中心扩散法,小编比较水就给大家讲讲动态规划吧! dp的话,主要就是推出dp方程:以上是具体

算法_Longest Palindromic Substring(寻找最长回文字串)

题目:Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring. 题解:首先需要清楚什么是“回文“(不知道这个翻译对不对?)字符串!回文字符串关于某一个字符对称,在左右两边与中心相同距离的字符相等. 那么还需要

ural 1297 O(nlogn) 后缀数组求最长回文字串

把原串复制一份反过来接在原串后面,中间用没出现过的字符隔开,然后跑后缀数组,在原创枚举每一个位为回文中心(分奇偶讨论),则回文串长度相当于原串与反串对应位置的lcp,所以先用ST预处理,然后查询.复杂度O(nlogn) #include<iostream> #include<cstring> #include<set> #include<map> #include<cmath> #include<stack> #include<

【POJ3974】最长回文字串

在这里采用的是哈希+二分的方法. 根据回文串的性质可知,可以将回文分成奇回文和偶回文分别进行处理. 对于奇回文来说,每次枚举的端点一定是重合的,因此只需计算出端点左右公共的长度是多少即可,因此二分的是以该枚举点为中心的左半边共有多少个字符. 对于偶回文来说,每次枚举的端点不一定是相等的,因此在二分的是以当前枚举点开始(包括当前枚举点)向右最多有多少个字符. 另外,之所以能够采用二分是因为当前需判断的左右串是否是回文这个问题具有单调性,即:从最大点处开始之后均不是回文,小于最大值均是回文. 在比较

java 最长回文字串

package string.string1_6; public class LongestPalidrome { /** * 使用常规方法, 以字符串的每一个字符作为中心进行判断, 包括奇数和偶数的情况 * 最坏时间复杂度为O(N) , 空间复杂度O(1) */ public static int longestPalidrome(String s) { if(s == null || s.length() <= 0) return 0 ; int max = 0 ; for(int i=0