0005. Longest Palindromic Substring (M)

Longest Palindromic Substring (M)

题目

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

题意

给定一个字符串s,要求输出该字符串中的最大回文子串。

思路

  1. 暴力法。复杂度\(O(N^3)\)
  2. 动态规划法。设若P[i][j]==true,则字符串s中从下标i到下标j构成的子串为回文子串,则很容易得到状态转移方程及边界条件如下,复杂度为\(O(N^2)\)

\[P[i][j] = (P[i+1][j-1] \ \&\&\ s[i] == s[j]) \]

\[P[i][j] = \begin{cases} true, &i==j\\ true, &j==i+1 \ \&\&\ s[i]==s[j]\\ false, &j==i+1 \ \&\&\ s[i]\ !=s[j] \end{cases} \]

  1. 中间扩展法。遍历字符串的每一个字符,奇数子串以该字符为中心向两边扩展搜索,直到两端字符不相同;偶数子串以该字符与下一个字符为中心向两遍扩展搜索,直到两端字符不相同。复杂度为\(O(N^2)\)
  2. 马拉车算法 - Manacher's Algorithm,复杂度为\(O(N)\)

代码实现

Java

动态规划

class Solution {
    public String longestPalindrome(String s) {
        // 特殊情况排除
        if (s == null || s.isEmpty()) {
            return "";
        }
        
        int maxLen = 0;
        int left = 0;
        int right = 0;
        boolean[][] P = new boolean[s.length()][s.length()];
        
        // 以子串长度及开始位置的变化进行循环
        for (int len = 1; len <= s.length(); len++) {
            for (int i = 0; i + len - 1 < s.length(); i++) {
                int j = i + len - 1;
                // 根据状态转移方程及边界条件生成数组P的值
                if (len == 1) {
                    P[i][j] = true;
                } else if (len == 2) {
                    P[i][j] = (s.charAt(i) == s.charAt(j));
                } else {
                    P[i][j] = (P[i + 1][j - 1] && s.charAt(i) == s.charAt(j));
                }
                if (P[i][j] && len > maxLen) {
                    maxLen = len;
                    left = i;
                    right = j;
                }
            }
        }
        return s.substring(left, right + 1);
    }
}

中间扩展

class Solution {
    public String longestPalindrome(String s) {
        // 特殊情况处理
        if (s == null || s.isEmpty()) {
            return "";
        }
        
        int maxLen = 0;
        int left = 0;
        int right = 0;
        
        for (int i = 0; i < s.length(); i++) {
            // 奇数子串情况
            int len1 = fromCenter(i, i, s);
            // 偶数子串情况
            int len2 = fromCenter(i, i + 1, s);
            int len = Math.max(len1, len2);
            if (len > maxLen) {
                maxLen = len;
                left = i - (len - 1) / 2;
                right = i + len / 2;
            }
        }
        return s.substring(left, right + 1);
    }

    // 从目标字符向两边扩展,返回长度
    public int fromCenter(int i, int j, String s) {
        while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)){
            i--;
            j++;
        }
        return j - i - 1;
    }
}

Manacher

class Solution {
    public String longestPalindrome(String s) {
        // 特殊情况排除
        if (s == null || s.isEmpty()) {
            return "";
        }

        String t = transform(s);
        int[] p = new int[t.length()];
        // 中心位置
        int C = 0;
        // 右边界
        int R = 0;
        // 回文串最大半径
        int maxLen = 0;
        // 最长回文串对应中心
        int pos = 0;

        for (int i = 0; i < t.length(); i++) {
            // i关于C的对称点
            int j = 2 * C - i;
            
            // 分三种情况进行赋值
            p[i] = R >= i ? Math.min(p[j], R - i) : 0;
            
            // 回文半径可增加的情况
            while (i + p[i] + 1 < t.length() && i - p[i] - 1 >= 0 
                    && t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) {
                p[i]++;
            }

            if (p[i] > maxLen) {
                maxLen = p[i];
                pos = i;
            }
            
            // 更新中心点和右边界
            if (i + p[i] > R) {
                C = i;
                R = i + p[i];
            }
        }

        int left = (pos - maxLen) / 2;
        int right = (pos + maxLen - 1) / 2;
        return s.substring(left, right + 1);
    }

    // 字符串转换
    public String transform(String s) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            builder.append('#').append(s.charAt(i));
        }
        builder.append('#');
        return builder.toString();
    }
}

JavaScript

动态规划

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  if (!s.length) return ''

  let max = 0
  let ends = [0, 0]
  let isPalin = []
  for (let i = 0; i < s.length; i++) {
    isPalin[i] = []
    isPalin[i][i] = true
  }

  for (let len = 2; len <= s.length; len++) {
    for (let i = 0; i + len - 1 < s.length; i++) {
      let j = i + len - 1
      if (s[i] !== s[j]) {
        isPalin[i][j] = false
      } else {
        isPalin[i][j] = len === 2 ? true : isPalin[i + 1][j - 1]
      }
      if (isPalin[i][j] && j - i + 1 > max) {
        max = j - i + 1
        ends = [i, j]
      }
    }
  }

  return s.slice(ends[0], ends[1] + 1)
}

中间扩展

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  let max = 0
  let ends = []

  for (let i = 0; i < s.length; i++) {
    let len = Math.max(findLength(s, i, i), findLength(s, i, i + 1))
    if (len > max) {
      max = len
      ends = [i - Math.floor((len - 1) / 2), i + Math.floor(len / 2)]
    }
  }

  return s.slice(ends[0], ends[1] + 1)
}

let findLength = function (s, i, j) {
  while (i >= 0 && j < s.length && s[i] === s[j]) {
    i--
    j++
  }
  return j - i - 1
}

Manacher

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  if (!s.length) return ''
  let ends = manacher(transform(s))
  return s.slice(ends[0], ends[1] + 1)
}

let manacher = function (s) {
  let maxRadius = -1
  let center = 0
  let right = 0
  let radius = new Array(s.length).fill(0)
  let ends = []

  while (right < s.length) {
    let left = 2 * center - right
    radius[right] = right > center + radius[center] ? 0 : Math.min(radius[left], center + radius[center] - right)
    while (
      right + radius[right] + 1 < s.length &&
      right - radius[right] - 1 >= 0 &&
      s[right + radius[right] + 1] === s[right - radius[right] - 1]
    ) {
      radius[right]++
    }
    if (radius[right] > maxRadius) {
      maxRadius = radius[right]
      ends = [right - radius[right], right + radius[right]]
    }
    if (right + radius[right] > center + radius[center]) {
      center = right
    }
    right++
  }

  return [Math.floor(ends[0] / 2), Math.floor(ends[1] / 2) - 1]
}

let transform = function (s) {
  let t = '#'
  for (let c of s) {
    t += c + '#'
  }
  return t
}

参考 - Manacher

博客园 - BIT祝威

posted @ 2020-06-15 11:31  墨云黑  阅读(25)  评论(0编辑  收藏  举报