回文串 O(n)算法 Manacher算法(第一部分)

0.序

求一个字符串中最长的回文串,O(n)算法Manacher。该算法很简单,比后缀树、后缀数组简单多了,比KMP也简单多了。而且效率很高是O(n)。本文翻译自

http://leetcode.com/2011/11/longest-palindromic-substring-part-i.html

http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html

是我看到的讲的最通俗易懂的一篇文章(包含部分)。下面是翻译的,我英语不好,英语稍微差不多的,还是建议读原文。

1. 最长回文字串(第1部分)

1.1 什么是回文串

首先,你需要知道什么是回文,就是正着读反正读,是一样的。比如,"aba"是回文串,"abc"不是回文串。

1.2 常见的错误

有些人试着想出了一个非常快的解决办法,但是这个办法是有缺陷的(不过可以被轻松的改正)

        反转S得到S‘。在S和S’中找到最长的公共字串,这一定是最长的回文串。

这个看起来挺好的。我们看看下面的例子:

S=“caba"  S'="abca"

最长的公共字串是"aba",这也是最长的回文串。

让我们试一下另外的例子:

S = “abacdfgdcaba”, S’ = “abacdgfdcaba”

最长的公共字串是"abacd"  ,但是,这不是一个回文串。

我们可以看到,当存在一个反转非回文字串和原来的串的某部分相同时,最长的公共字串的方法失效了。为了能够改正这个问题,我们需要在每次找到最长公共字串的时候,检查这个字串,如果它不是回文串,我们就查找下一个选项。

我们可以利用动态规划,在O(N2)的时间复杂度和O(N)的空间复杂度,求出最长公共子序列。详细内容请看http://en.wikipedia.org/wiki/Longest_common_substring

1.3  暴力解决

最明显的暴力方法是,从查找所有的字符串,每个字符串检查是否是回文串。一共有  C(N, 2)个字符串,所有要O(N3)的时间复杂度。

1.4  动态规划解决

时间复杂度和空间复杂度都是 O(N 2 )
我们可以利用动态规划提高暴力方法的时间复杂度。首先,我们要思考在监测回文串时,怎样才能避免不必要的重复计算。看看这个字符串”ababa",假如我们已经知道字符串"bab“是回文串,那么很明显,”ababa"也是一个回文串。
用规范的式子写一下
if Si … Sj 是回文串 
    P[ i, j ] ← true
 else   
     P[ i, j ] ← false;
那么可以得到下面的式子
if ( P[ i+1, j-1 ] and Si = Sj )
   P[i.j]=true;
else
   P[i,j=false;
初始式子是
   P[ i, i ] ← true
   P[ i, i+1 ] ← ( Si = Si+1 )
这是一个非常直接的动态规划解决方法,我们先初始化一个和两个字母的回文串,然后按照我们的状态转移式子计算所有三个字母的回文串,一直到最后。
下面我们给出了时间复杂度和空间复杂度都是O(N2)
string longestPalindromeDP(string s) {
  int n = s.length();
  int longestBegin = 0;
  int maxLen = 1;
  bool table[1000][1000] = {false};
  for (int i = 0; i < n; i++) {
    table[i][i] = true;
  }
  for (int i = 0; i < n-1; i++) {
    if (s[i] == s[i+1]) {
      table[i][i+1] = true;
      longestBegin = i;
      maxLen = 2;
    }
  }
  for (int len = 3; len <= n; len++) {
    for (int i = 0; i < n-len+1; i++) {
      int j = i+len-1;
      if (s[i] == s[j] && table[i+1][j-1]) {
        table[i][j] = true;
        longestBegin = i;
        maxLen = len;
      }
    }
  }
  return s.substr(longestBegin, maxLen);
}

1.5  另一个比较好的解决方法

实际上,有一个比较简单的解决方法是 O(N 2 ) 的时间复杂度,O(1)的空间复杂度。
我们观察到每一个回文串都有一个中心,这样,一个回文串可以从这个中心扩展。一共有2N-1个中心。
为什么是2N-1个中心而不是N个中心?因为中心可能是两个字母,比如字符串“abba"。
下面是这个算法的代码
string expandAroundCenter(string s, int c1, int c2) {
  int l = c1, r = c2;
  int n = s.length();
  while (l >= 0 && r <= n-1 && s[l] == s[r]) {
    l--;
    r++;
  }
  return s.substr(l+1, r-l-1);
}
 
string longestPalindromeSimple(string s) {
  int n = s.length();
  if (n == 0) return "";
  string longest = s.substr(0, 1);  // a single char itself is a palindrome
  for (int i = 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;
  }
  return longest;
}
进一步思考:
是否存在一个O(N)时间复杂度的解决办法?必须存在,请看下一篇博文。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值