`
mabusyao
  • 浏览: 253297 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

(转)最长回文字串算法

阅读更多

来自(http://blog.163.com/zhaohai_1988/blog/static/2095100852012716105847112/)

 

最长回文子串 

 

 
 
    最长回文子串是最初我在网易笔试的时候遇见的,当时天真的把原字符串S倒转过来成为S‘,以为这样就将问题转化成为了求S和S’的最长公共子串的问题,而这个问题是典型的DP问题,我也在前面的文章中介绍了3中解决这个问题的方法。但是非常可惜,后来才知道这个算法是不完善的。那么到底为什么呢?听我慢慢道来。

S=“c a b a”  那么  S' = “a b a c”, 这样的情况下 S和 S‘的最长公共子串是aba。没有错误。

    但是当 S=“abacdfgdcaba”, 那么S’ = “abacdgfdcaba”。 这样S和S‘的最长公共子串是abacd。很明显abacd并不是S的最长回文子串,它甚至连回文都不是。

    现在是不是都明白为什么最长回文子串不能转化成为最长公共子串问题了。当原串S中含有一个非回文的串的反序串的时候,最长公共子串的解法就是不正确的。正如上一个例子中S既含有abacd,又含有abacd的反串cdaba,并且abacd又不是回文,所以转化成为最长公共子串的方法不能成功。除非每次我们求出一个最长公共子串的时候,我们检查一下这个子串是不是一个回文,如果是,那这个子串就是原串S的最长回文子串;如果不是,那么就去求下一个次长公共子串,以此类推。

 

最长回文子串有很多方法,分别是1暴力法,2 动态规划, 3 从中心扩展法,4 著名的manacher算法。下面我将分别介绍几种方法。

 

方法一 暴力法

遍历字符串S的每一个子串,去判断这个子串是不是回文,是回文的话看看长度是不是比最大的长度maxlength大。遍历每一个子串的方法要O(N2),判断每一个子串是不是回文的时间复杂度是O(N),所以暴利方法的总时间复杂度是O(N3)。

 

方法二 动态规划 时间复杂度O(N2), 空间复杂度O(N2)

    动态规划就是暴力法的进化版本,我们没有必要对每一个子串都重新计算,看看它是不是回文。我们可以记录一些我们需要的东西,就可以在O(1)的时间判断出该子串是不是一个回文。这样就比暴力法节省了O(N)的时间复杂度哦,嘿嘿,其实优化很简单吧。

P(i,j)为1时代表字符串Si到Sj是一个回文,为0时代表字符串Si到Sj不是一个回文。

P(i,j)= P(i+1,j-1)(如果S[i] = S[j])。这是动态规划的状态转移方程。

P(i,i)= 1,P(i,i+1)= if(S[i]= S[i+1])

string longestPalindromeDP(string s){

  intn = s.length();

  intlongestBegin =0;

  intmaxLen =1;

  booltable[1000][1000]={false};

  for(inti =0; i < n; i++){

    table[i][i]=true;//前期的初始化

  }

  for(inti = 0; i < n-1; i++) {

    if(s[i] == s[i+1]) {

      table[i][i+1] = true; //前期的初始化

      longestBegin = i;

      maxLen = 2;

    }

  }

  for(intlen = 3; len <= n; len++) {

    for(inti = 0; i < n-len+1; i++) {

      intj = i+len-1;

      if(s[i] == s[j] && table[i+1][j-1]) {

        table[i][j] = true;

        longestBegin = i;

        maxLen = len;

      }

    }

  }

  returns.substr(longestBegin, maxLen);

}

 

方法三 中心扩展法

    这个算法思想其实很简单啊,时间复杂度为O(N2),空间复杂度仅为O(1)。就是对给定的字符串S,分别以该字符串S中的每一个字符C为中心,向两边扩展,记录下以字符C为中心的回文子串的长度。但是有一点需要注意的是,回文的情况可能是 a b a,也可能是 a b b a。

string expandAroundCenter(string s,intc1,intc2){

  intl = c1, r = c2;

  intn = s.length();

  while(l >=0&& r <= n-1&& s[l]== s[r]){

    l--;

    r++;

  }

  returns.substr(l+1, r-l-1);

}

string longestPalindromeSimple(string s){

  intn = s.length();

  if(n ==0)return"";

  string longest = s.substr(0,1);  // a single char itself is a palindrome

  for(inti = 0; i < n-1; i++) {

    string p1 = expandAroundCenter(s, i, i);

    if(p1.length() > longest.length())

      longest = p1;

    string p2 = expandAroundCenter(s, i, i+1);

    if(p2.length() > longest.length())

      longest = p2;

  }

  returnlongest;

}


  方法四 传说中的Manacher算法。时间复杂度O(N)

    这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。

    算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
    原串:    w aa bwsw f d
    新串:           #  w  # a # a #  b  # w # s # w #  f  # d #
辅助数组P:1  2  1 2 3 2 1  2  1  2 1 4 1 2 1  2 1 2 1

这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。

现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。

    那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,其中id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。

    然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx > i,那么

P[i] >= MIN(P[2 * id - i], mx - i)。就是这个串卡了我非常久。实际上如果把它写得复杂一点,理解起来会简单很多:

 

//记j = 2 * id - i,也就是说 j 是 i 关于 id 的对称点。
if (mx - i > P[j]) 
    P[i] = P[j];
else /* P[j] >= mx - i */
    P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。

当 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之后的部分是否对称,就只能老老实实去匹配了。

由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)

#include<vector>
#include<iostream>
usingnamespace std;

constint N=300010;
int n, p[N];
char s[N], str[N];

#define _min(x, y)((x)<(y)?(x):(y))

void kp()
{
int i;
int mx =0;
int id;
for(i=n; str[i]!=0; i++)
str[i]=0;//没有这一句有问题。。就过不了ural1297,比如数据:ababa aba
for(i=1; i<n; i++)
{
if( mx > i )
p[i]= _min( p[2*id-i], p[id]+id-i );
else
p[i]=1;
for(; str[i+p[i]]== str[i-p[i]]; p[i]++)
;
if( p[i]+ i > mx )
{
mx = p[i]+ i;
id = i;
}
}
}

void init()
{
int i, j, k;
str[0]='$';
str[1]='#';
for(i=0; i<n; i++)
{
str[i*2+2]= s[i];
str[i*2+3]='#';
}
n = n*2+2;
s[n]=0;
}

int main()
{
int i, ans;
while(scanf("%s", s)!=EOF)
{
n = strlen(s);
init();
kp();
ans =0;
for(i=0; i<n; i++)
if(p[i]>ans)
ans = p[i];
printf("%d\n", ans-1);
}
return0;
}

 

if( mx > i)

p[i]=MIN( p[2*id-i], mx-i);

 

就是当前面比较的最远长度mx>i的时候,P[i]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?

(下面的部分为图片形式)


分享到:
评论

相关推荐

    查找一个字符串中的最长回文子串,这里采用的是Manacher算法

    查找一个字符串中的最长回文子串,这里采用的是Manacher算法 比如:cababcaac的最长回文子串就是caac 其中的aba bab也都是回文子串 (Manacher算法) 效率很高的一种查找算法,效率可以达到O(2n+1)

    LeetCode5. 最长回文子串(双指针、中心扩展算法)

    在LeetCode的第5题《最长回文子串》中,目标是找到给定字符串` s `中的最长回文子串。回文串是指正读反读都能读通的字符串,例如 "aba" 和 "abba" 都是回文串。本题要求的解决方案需在字符串长度不超过1000的限制内...

    C# 文本对比算法比较两个字符串的不同

    其他算法,如Longest Common Subsequence(最长公共子序列)和Diff Match Patch,也能提供类似的功能。 2. **生成差异数组**:算法会返回一个表示差异的数组或集合,其中包含了插入、删除或替换的操作。每个元素...

    DELPHI 计算两个字符串相似度 LCS算法(附源代码)

    LCS算法是一种找出两个序列中最长的相同子序列的算法,它不考虑子序列的顺序,对于字符串而言,就是找到最长的相同子字符串。 首先,我们需要理解LCS算法的基本思想。假设我们有两个字符串S1和S2,LCS算法会找到S1...

    模式匹配的KMP算法

    模式匹配的KMP算法的主要思想是对目标串进行预处理,生成一个辅助数组next[],该数组记录了模式串中每个字符的最长末尾公共子串的长度。然后,通过比较目标串和模式串,根据next[]数组中的信息,快速地找到模式串在...

    java算法大全[文字版]中文

    7. **字符串处理**:如KMP匹配算法、Rabin-Karp滚动哈希等,这些在文本处理、搜索和比较中很常见。 8. **位运算**:高效地使用位运算可以提高算法效率,例如在解决集合操作、编码和解码问题时。 9. **分治算法**:...

    《算法导论》第三版英文版-带书签目录文字版1313页完整版

    本书涵盖了广泛的算法主题,包括排序、搜索、图算法、动态规划、字符串处理、计算几何等。这些算法在实际编程和软件开发中有着广泛应用,是解决复杂问题的关键工具。书中的每个章节都精心设计了详细的实例和习题,...

    算法模板.pdf

    - KMP算法(Knuth-Morris-Pratt):一种用于字符串匹配的算法,可以在O(n+m)时间复杂度内完成搜索。 - KMP拓展:对KMP算法进行拓展和应用。 - 字典树(Trie):一种用于快速检索字符串数据集中的键的技术。 - AC...

    最长公共子序列c#学习案例(完整版)含Excel数据分析

    这个算法可以应用到文字录入测试的应用中,测试文字录入内容,常用的处理方法是关键字检测发,通过检测word文件中的关键字,可以测出考生操作的大概结果,但是很不准确,利用最长公共子序列算法进行文字录入测试,...

    字符串面试题整理

    6. **最长回文字串**:寻找字符串中最长的回文子串,可以使用动态规划或者Manacher's Algorithm,后者具有线性时间复杂度。 7. **字符串匹配**: - 暴力匹配:逐个字符比较,时间复杂度O(n*m),其中n是主串长度,m...

    FuzzyStrings:.NET的模糊字符串算法。

    .NET的模糊字符串算法的集合。 这部分来自多个开源。 有关归因,请参见各个算法类。 开发人员可能希望利用此libray或人为的字符串比较扩展方法中包含的一种或多种算法,如下所示: bool isEqual = input . ...

    算法竞赛的好书

    2. 数组和字符串:讲解数组、字符数组的使用,以及如何处理字符串,例如查找最长回文子串等。 3. 函数和递归:探讨如何编写简单的函数,使用结构体的函数,以及递归的基本概念和编程技巧。递归是一种重要的编程技巧...

    查找重复字符串代码

    3. 提取重复信息:记录下重复子串及其出现的位置,可以进一步计算其出现的频率,或者找出最长的重复子串。 在压缩包中的"重复字串"文件很可能是示例代码或测试数据,用于演示如何使用后缀数组查找重复字符串。通过...

    Ruby语言中最长回文子序列求解

    内容概要:讲解如何采用 Ruby 编程语言通过动态规划方法解决问题——在一个指定的小写英文字母字符串里找到最长得回文子序列及其具体长度。详尽阐述了动态规划的策略以及状态方程,并附带示例代码。 适用人群:对 ...

    我的OI算法总结。原创。

    3. **字符串处理**:KMP算法、Manacher's算法用于高效匹配模式串,Z算法、Rabin-Karp算法用于文本模式匹配。 4. **递归与回溯**:在解决组合优化问题和搜索问题时常用,如八皇后问题、N皇后问题、迷宫问题等。 5. ...

    文字比对功能不同文字颜色标红

    综上所述,实现“文字比对功能不同文字颜色标红”主要涉及JavaScript中的字符串操作、字符比较、动态规划算法、DOM操作以及用户体验优化。在实际应用中,可以根据需求选择适当的方法或库,并结合前端技术如CSS和HTML...

    LeetCode算法设计

    2. **最长回文串[M]**:给定一个包含大写和小写英文字母的字符串,找出其中最长的回文子串。一种有效的解法是使用中心扩展法。 #### 四、链表 **链表**是一种常见的数据结构,涉及到各种链表的操作。 1. **两个...

    百度2014校招软件研发工程师笔试题(湖北武汉)

    2. 遍历数组,使用动态规划算法来计算最长回文字串的长度。 3. 返回最长回文字串。 3. 数轴上有n个点a[0]、a[1]……a[n-1],一个长度为L的绳子,最多可以覆盖多少的点。 算法思想: 1. 将点排序。 2. 使用贪心...

Global site tag (gtag.js) - Google Analytics