leetcode 5. 最长回文子串: Manacher 算法

189 阅读1分钟

给你一个字符串 s,找到 s 中最长的回文子串。

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/lo… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

1. 中心扩散法

以奇数长度回文串为例:
遍历字符串 ss, 对于每一个字符 s[i]s[i], 定义 l=r=il = r = i
其中 ll 表示回文串的最左端的下标, rr 表示回文串最右端的下标
每当 s[l]=s[r]s[l] = s[r] 时, l=l1,r=r+1l = l - 1, r = r + 1. 同时 rl+1r - l + 1 表示回文子串的长度

var longestPalindrome = function(s) {
  const n = s.length
  let st = 0, len = 0
  for (let i = 0, l, r; i < n; i ++) {
    // odd
    l = r = i
    while (l >= 0 && r < n && s[l] === s[r]) {
      l --, r ++
    }
    l ++, r --
    if (len < r - l + 1) {
      st = l, len = r - l + 1
    }
    
    // even
    l = i, r = i + 1
    while (l >= 0 && r < n && s[l] === s[r]) {
      l --, r ++
    }
    l ++, r --
    if (len < r - l + 1) {
      st = l, len = r - l + 1
    }
  }
  return s.slice(st, st + len)
};

2. Manacher

2.1 朴素 Manacher

d1[i]d1[i] 指的是以 ii 为中心的回文字符串(奇数)的一半(向上取整), 如 abcdcbaabcdcbad1[4]=4d1[4] = 4
d2[i]d2[i] 指的是以 ii 为中心偏右的回文字符串(偶数)的一半, 如 abcddcbaabcddcbad2[4]=4d2[4] = 4

var longestPalindrome = function(s) {
  const n = s.length
  const d1 = new Array(n).fill(0)
  cosnt d2 = new Array(n).fill(0)
  
  let st = 0, len = 0
  for (let i = 0; i < n; i ++) {
    d1[i] = 1
    while (i - d1[i] >= 0 && i + d1[i] < n && s[i - d1[i]] === s[i + d1[i]]) {
      d1[i] ++
    }
    
    d2[i] = 0
    while (i - d2[i] - 1 >= 0 && i + d2[i] < n && s[i - d2[i] - 1] === s[i + d2[i]]) {
      d2[i] ++
    }
  }
};

2.2 优化的 Manacher

遍历字符串 ss, s[i]s[i] 表示遍历到的当前项, s[l,r]s[l, r] 表示当前已知的所有回文串中最靠右的回文串.

  • 如果 ii 不在 rr 的右侧, 则表明 ii[l,r][l, r] 之间. 对于 s[i]s[i] 来说, 存在一个 s[j]s[j] 与其在 [l,r][l, r] 中对称, 如下图所示: image.png 此时对于 s[i]s[i] 来说, 其 d1[i]d1[i] 有两种可能:
    1. 如果以 s[j]s[j] 为中心的回文子字符串的范围在 [l,r][l, r] 中, 那么 d1[i]=d1[j]d1[i] = d1[j]
    2. 如果以 s[j]s[j] 为中心的回文子字符串的范围超出了 [l,r][l, r], 则为 d1[i]d1[i] 赋初始值: d1[i]=ri+1d1[i] = r - i + 1, 后再以朴素版 Manacher 对 d1[i]d1[i] 进行求值
  • 如果 iirr 的右侧, 则只能直接使用 Manacher 算法对 d1[i]d1[i] 进行求值
var longestPalindrome = function(s) {
  const n = s.length
  const d1 = new Array(n + 1).fill(0)
  const d2 = new Array(n + 1).fill(0)

  let st = 0, len = 0
  for (let i = 0, l = 0, r = -1, k; i < n; i ++) {
    // odd
    k = (i > r) ? 1 : Math.min(d1[l + r - i], r - i + 1)
    while (i - k >= 0 && i + k < n && s[i - k] === s[i + k]) {
      k ++
    }
    d1[i] = k --
    if (r < i + k) {
      l = i - k
      r = i + k
    }
    
    // even
    k = (i > r) ? 0 : Math.min(d2[l + r - i + 1], r - i + 1)
    while (i - k - 1 >= 0 && i + k < n && s[i - k - 1] === s[i + k]) {
      k ++
    }
    d2[i] = k --
    if (r < i + k) {
      r = i + k
      l = i - k - 1
    }

    if (len < d2[i] * 2) {
      st = i - d2[i], len = d2[i] * 2
    }
    if (len < d1[i] * 2 - 1) {
      st = i - d1[i] + 1, len = d1[i] * 2 - 1
    }
  }
  
  return s.slice(st, st + len)
};