1. //给你一个字符串 s,找到 s 中最长的回文子串。
    2. //
    3. //
    4. //
    5. // 示例 1:
    6. //
    7. //
    8. //输入:s = "babad"
    9. //输出:"bab"
    10. //解释:"aba" 同样是符合题意的答案。
    11. //
    12. //
    13. // 示例 2:
    14. //
    15. //
    16. //输入:s = "cbbd"
    17. //输出:"bb"
    18. //
    19. //
    20. // 示例 3:
    21. //
    22. //
    23. //输入:s = "a"
    24. //输出:"a"
    25. //
    26. //
    27. // 示例 4:
    28. //
    29. //
    30. //输入:s = "ac"
    31. //输出:"a"
    32. //
    33. //
    34. //
    35. //
    36. // 提示:
    37. //
    38. //
    39. // 1 <= s.length <= 1000
    40. // s 仅由数字和英文字母(大写和/或小写)组成
    41. //
    42. // Related Topics 字符串 动态规划
    43. // 👍 3305 👎 0

    在题解中已有动态规划和中心扩散的方法,这题主要是学习manacher算法。
    在题解的视频中也有清晰的讲解,建议跟随视频把题解代码多抄几遍,跟着debug走几遍即可了解。

    有这么几个特殊参数需要提前解释
    maxRight: 回文子串的最大右边界,回文子串扩散到最右的位置
    center: 最大右边界对应的center
    i: 当前要计算回文子串的位置
    mirror: 当前位置对center的镜像位置

    manacher的原理就是使用动态规划的思想,计算i的时候,可以参考mirror位置的结果。

    分情况讨论:
    如果i>=maxRight, 使用中心扩散的方法找最大右边界
    如果i<maxRight, 又分三种情况

    1. maxRight - i > dp[mirror]

    image.png
    根据dp[mirror]已经计算出来的长度,回文的区域没有超过maxRight,因此整个字符串对于center是对称的。
    而且dp[mirror]已经扩散到最大,说明mirror-(dp[mirror]+1)和mirror+(dp[mirror]+1)两个字符肯定是不相同的,不然dp[mirror]就不是mirror为中心的最长回文子串了。
    因此此时dp[i]的长度就可以直接取dp[mirror]

    1. maxRight - i == dp[mirror]

    image.png
    此时根据图像可以看出,mirror右边界和左边界的字母是不同的,根据maxRight的对称性,i左边界的字母和mirror右边界是相同的,maxRight+1和maxRight镜像位置-1的位置一定是不同的。
    那么就有可能使i的左边界和右边界向外扩散。
    因此i位置的回文串长度至少是maxRight - i,实际长度还要根据该结果向外扩展。

    1. maxRight - i < dp[mirror]

    image.png
    如果dp[mirror]的值已经超出了maxRight和center指示的回文串的范围,那么说明maxRight+1和maxRight镜像位置-1的字母一定不相同。
    如果相同,maxRight的位置应该+1。
    因此,根据回文串的对称性,i的左右边界一定就停在maxRight上,再无法向外扩展了。

    1. class Solution {
    2. public String longestPalindrome(String s) {
    3. int len = s.length();
    4. // 如果字符串长度小于2, 那直接就是一个回文串
    5. if (len < 2) {
    6. return s;
    7. }
    8. // 将字符串填充, 如 'abacadv' 填充为 '#a#b#a#c#a#d#v#'
    9. String str = addDevise(s, '#');
    10. int sLen = str.length();
    11. // maxRight 标记的是回文串可以到达的最大右边界
    12. int maxRight = 0;
    13. // center是最大右边界对应的回文中点
    14. int center = 0;
    15. // 最长回文串的起始位置
    16. int begin = 0;
    17. // 最长回文串的长度
    18. int maxLength = 1;
    19. // dp存储的是i位置为中心,最长回文串的半径
    20. int[] dp = new int[sLen];
    21. char[] chars = str.toCharArray();
    22. for (int i = 0; i < sLen; i++) {
    23. // 如果i没超过回文串的最大右边界
    24. if (i < maxRight) {
    25. // mirror是i关于center对称的位置
    26. int mirror = 2 * center - i;
    27. // dp[i]至少是dp[mirror]和(maxRight和i的距离)中的最小值
    28. dp[i] = Math.min(dp[mirror], maxRight - i);
    29. }
    30. // 尝试向两边扩散
    31. int left = i - (1 + dp[i]);
    32. int right = i + (1 + dp[i]);
    33. while (left >= 0 && right < sLen && chars[left] == chars[right]) {
    34. left --;
    35. right ++;
    36. dp[i] ++;
    37. }
    38. // 维护最大右边界和center
    39. if (i + dp[i] > maxRight) {
    40. maxRight = i + dp[i];
    41. center = i;
    42. }
    43. // 维护最长回文子串
    44. if (dp[i] > maxLength) {
    45. maxLength = dp[i];
    46. begin = (i - maxLength) / 2;
    47. }
    48. }
    49. return s.substring(begin, begin + maxLength);
    50. }
    51. public String addDevise(String s, char ch) {
    52. StringBuilder stringBuilder = new StringBuilder();
    53. stringBuilder.append(ch);
    54. for (int i = 0; i < s.length(); i++) {
    55. stringBuilder.append(s.charAt(i));
    56. stringBuilder.append(ch);
    57. }
    58. return stringBuilder.toString();
    59. }
    60. }