`
mabusyao
  • 浏览: 247335 次
  • 性别: 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)

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

    比较两个字符串的相似度,利用LCS算法计算出两个字符串的最长公序列,根据最长公序列得出相似度,例如: 字符串1:1234 字符串2:51234,则他们的相似度为:4*2/(4+5)。

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

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

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

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

    高效算法:竞赛、应试与提高必修128例.[法] Christoph Dürr Jill-Jênn Vie(带书签文字版).pdf

    2 8 字符串的最长回文子串:Manacher 算法 42 第 3 章 序列 44 3 1 网格中的最短路径 44 3 2 编辑距离(列文斯登距离45 3 3 最长公共子序列 47 3 4 升序最长子序列 49 3 5 两位玩家游戏中的必胜策略 52 第 4 章 数组...

    深入解析最长公共子串

    题目:如果字符串一的所有字符... 分析:求最长公共子串(Longest Common Subsequence, LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。完整介绍动态规划将需要很长的篇幅

    leetcode算法题主函数如何写-algorithm:关注算法,题目来源于LeetCode。使用Java8来实现,涵盖:数组、链表、栈、堆、

    如果当前循环没有找到最大回文字串,则将遍历的回文字串长度--,继续循环 时间复杂度:O(n^3) 空间复杂度:O(1) 07. 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 方法:每次获取剩余整数...

    华为机试华为OD机试算法题Python源码(41道).zip

    汽水瓶.py,求int型正整数在内存中...字符串反转.py,字符串分割.py,字符串合并处理.py,字符串加密.py,字符串加密2.py,字符串排序.py,字符串运用-密码截取.py,字符串最后一个单词的长度.py,字符个数统计.py,坐标移动.py

    ds-and-algorithms:数据结构和算法问题,以及针对JavaScript,TypeScript,Go和Java的解决方案说明和实现

    使用JavaScript,TypeScript,Go和Java的数据结构和算法问题 数据结构和算法问题,以及针对不同语言的解决方案说明和实现 :bar_chart: 按主题组织 二叉树 -硬 -中 -中 -中 -简单 -中 堆 -中 -困难 弦乐 简单 通过...

    leetcode

    147对链表进行插入排序去做148排序链表归并排序(TODO)回溯算法序号译文思路17电话号码的字母组合回溯之组合问题46全分回溯之排序问题77组合问题子串问题序号译文思路3无重复字串的最长字串滑动窗口5最长回文字串...

    leetcode分类-LeetCode:LeetCode刷题代码

    leetcode 分类 LeetCode刷题代码(每日更新) 分类别对题目答案进行记录,包含清晰的代码描述 ...5-最长回文字串 题目序号-题目名称 题目描述及示例 代码内容(include和using,在LeetCode中不需要添加)

    javalruleetcode-JavaInterviewChecklist:要检查的Java事项

    新字符串与文字字符串 - 字符串生成器 - 从 CTCI 读取 StringBuffer 与字符串生成器 DP 买卖股票的最佳时机 房屋强盗 爬楼梯 独特的路径 编辑距离- 背包问题 油漆围栏 二分查找 可用于单调递增/递减函数。 例如:...

    lrucacheleetcode-MySDEInterviewStudies:SDE编码面试研究

    (待定:研究选择排名算法) (待定:研究尝试) 塔式料斗问题 查找加起来为 X 的数字集 找到数组之间的交集 反转整数 模式匹配 * 旋转阵列 搜索建议系统 使用随机指针复制列表 关键路由器 设计井字游戏 * 重组字符...

    Python Trie树实现字典排序

    Trie树是一种很常用的树结构,它被广泛用于各个方面,比如字符串检索、中文分词、求字符串最长公共前缀和字典排序等等,而且在输入法中也能看到Trie树的身影。 什么是Trie树 Trie树通常又称为字典树、单词查找树或...

    Microsoft SQL Server 2008技术内幕:T-SQL查询(第二卷)

    《Microsoft SQL Server 2008技术内幕:T-SQL查询》内容丰富、文字简洁明快,列举的实例具有一定的难度,而且实用性很强,可以把它们作为解决实际问题的标准模式。阅读《Microsoft SQL Server 2008技术内幕:T-SQL...

    SQLServer2008技术内幕T-SQL查询包含源代码及附录A

    《Microsoft SQL Server 2008技术内幕:T-SQL查询》内容丰富、文字简洁明快,列举的实例具有一定的难度,而且实用性很强,可以把它们作为解决实际问题的标准模式。阅读《Microsoft SQL Server 2008技术内幕:T-SQL查询...

    Microsoft+SQL+Server+2008技术内幕:T-SQL查询_源代码及附录 中文版

    《Microsoft SQL Server 2008技术内幕:T-SQL查询》内容丰富、文字简洁明快,列举的实例具有一定的难度,而且实用性很强,可以把它们作为解决实际问题的标准模式。阅读《Microsoft SQL Server 2008技术内幕:T-SQL查询...

    上海电机学院C语言实训答案

    将需要加密的一行文字输入加密程序,程序根据加密表中的对应关系,可以简单地将输入的文字加密输出,对于表中未出现的字符则不加密。 运行示例: 输入:lajgdike,w 输出:ldjg,abice (12)编写程序验证以下说法:...

    数据结构(C++)有关练习题

    2、 请用C++编写一个算法,完成以下功能: a. 从键盘输入一段文字,以$作结束符号; b. 统计文字中的文本行数,字母,数字以及其他符号的数量,并在屏幕上显示; 3、 请用C++编写一个算法,完成矢量的...

    PL/SQL 基础.doc

    1)字符型文字(字符串) 'tom' (单引号) 'tom''s pen' ''为2个单引号(标识转义) 为tom's pen 2)数字型 123 -4 +56 0 9.0 1.23E5 9.8e-3 3)布尔型 TRUE FALSE NULL 7. 变量声明 语法 Var_name [CONSTANT](标识...

Global site tag (gtag.js) - Google Analytics