Manacher(马拉车)算法 Java语言实现 学习笔记

题目

动态规划思路:

public String longestPalindrome(String s) {
     if (s == null || s.length() == 1) return s;
     int n = s.length();
     int maxLength = 1;//最⼤⻓度回⽂串的⻓度
     int left = 0; //最⼤⻓度回⽂串的起始索引
     char[] chars = s.toCharArray();
     boolean[][] dp = new boolean[n][n]; //备忘录,矩阵表示该⼦串是否是回⽂(⾏索引为⼦串起点、列索引为⼦串终点)

      for (int subLen = 1; subLen <= n; ++subLen) {//⼦串⻓度从1到n
         for (int i = 0; i + subLen - 1 < n; ++i) {//遍历每⼀个起点
             int j = i + subLen - 1;//终点
                if (subLen == 1) {//单字符本身为回⽂
                 dp[i][j] = true;//
                    } else if (subLen == 2) {//双字符直接⽐对
                    dp[i][j] = (chars[i] == chars[j]);
                     } else {//状态转移⽅程
                     dp[i][j] = (chars[i] == chars[j] && dp[i + 1][j - 1]);
                     }
         if (dp[i][j] && subLen > maxLength) {//记录当前最⼤回⽂串位置
                 maxLength = subLen;
                     left = i;
                 }
            }
     }
     return new String(chars, left, maxLength);
}

优化思路:

动态规划的解题思路有一些不必要的判断,它是遍历整个dp[n][n]这个地图了,时间复杂度和空间复杂度都是O(n^2),但是当你的最里面的两个字符dp[i][i+1]已经不是回文字符串了,那么以它为中心的字符串就没有判断的必要了,因此以这个思路莱优化我们的代码

中心扩展算法:

public String longestPalindrome(String s) {
     if (s == null || s.length() == 1) return s;
     char[] chars = s.toCharArray();
     int start = 0, maxLen = 1;//当前最⻓回⽂⼦串的起始位置及⻓度
     for (int i = 0; i < chars.length; i++) {//遍历每⼀个字符,尝试找到以其为中⼼的最⻓回⽂串
         int len1 = expandAroundCenter(chars, i, i);//奇数⻓度,以i为中⼼扩散
         int len2 = expandAroundCenter(chars, i, i + 1);//偶数⻓度,以i、i+1为中⼼扩散
         int len = Math.max(len1, len2);
         if (len > maxLen) {
             start = i - (len - 1) / 2;
             maxLen = len;//当前最⼤⻓度
         }    
     }
         return new String(chars, start, maxLen);
}
//以left、right为中⼼可扩展的最⼤回⽂串⻓度
public int expandAroundCenter(char[] chars, int left, int right) {
     while (left >= 0 && right < chars.length && chars[left] == chars[right]) {
         left--;
         right++;
     }
 return right - left - 1;
}

最坏的情况下时间复杂度为O(n^2),空间复杂度为O(n);时间相当于动态规划的性能提升了不少

再优化:

问题:1.回文相互重叠的时候,会有大量的重复计算

           2.每次都要分别解决奇数和偶数长度的回文串

           3.有两层嵌套循环

Manacher算法:

            特点:线性时间内查找字符串中任意位置开始的所有回文子串

首先进行预处理

1.解决奇数偶数问题

在每个字符间插入'#'字符,使得每次只用解决偶数的问题

                                        eg: ababa

                                        插入后:#a#b#a#b#a#

2.解决头尾判断越界问题

只要给头尾插入不一样的字符,这样每次遍历到这个特殊字符的时候,就直接结束判断

                                        eg: ababa

                                        插入后:^#a#b#a#b#a#$

3.解决两层嵌套循环

                记录每个位置进行中心扩展的最大半径(类似于备忘录)

                ***通过镜像去填充数组,以达到O(n)的时间复杂度

class Solution {
     public String longestPalindrome(String s) {
         if (s == null || s.length() == 1) return s;
           //1.转成字符数组,添加特定字符
         char[] chars = s.toCharArray();
         char[] newChars = preProcess(chars);//预处理后的字符串
         int n = newChars.length;//newStr.length();
         int[] results = new int[n];//以该索引位置为中⼼的最⻓回⽂⻓度
         int center = 0;//初始选取0位置为中⼼
         int right = 0;//右边范围,初始为0
         //2.遍历新数组,记录每个位置i的最⻓回⽂半径results[i]
         for (int i = 1; i < n - 1; i++) { //依次遍历每⼀个位置计算该位置为中⼼的最⻓回⽂⻓度
         // 2.1 选定当前中⼼位置center及右边界right,以中⼼位置计算i的镜像位置i_mirror
             int i_mirror = 2 * center - i;//以center为中⼼与i对应的左侧镜像位置
 // 2.2 如果i不超过右边界, results[i] 取 right – i 与results[i_mirror] 的较⼩值
             if (i < right) {
             results[i] = Math.min(right - i, results[i_mirror]);//取 镜像的结果 和 i到右边界的距离 中较⼩的值
             } else results[i] = 0;//等于 right 的情况
 // 2.3 以i为中⼼, results[i]为半径再扩展⽐对,并更新results[i]
         while (newChars[i + 1 + results[i]] == newChars[i - 1 -results[i]])
                 results[i]++;
 // 2.4 如果 i+result[i] 超过右边界,则更新 center 和 right (拉⻋)
             if (i + results[i] > right) {
                 center = i;
                 right = i + results[i];
                 }
             }
 //3.遍历results[i]找到最⼤的回⽂半径
             int maxLen = 0;
             int centerIndex = 0;
             for (int i = 1; i < n - 1; i++) {
                 if (results[i] > maxLen) {
                     maxLen = results[i];
                     centerIndex = i;
                }
         }
 //4.换算成原始字符串中起始位置及⻓度,返回最⻓回⽂⼦串
             int start = (centerIndex - maxLen) / 2; //将⻢拉⻋下标换算成原字符串下标
                 return new String(chars,start,maxLen);;
                 }
 /*
 字符串预处理,添加分隔符及⾸尾标识
 数组⻓度⼤于等于1
 */
         public char[] preProcess(char[] chars) {
             int n = chars.length, index = 0;
             char[] newChars = new char[2 * n + 3];
             newChars[index++] = '^';
         for (int i = 0; i < n; i++) {
             newChars[index++] = '#';newChars[index++] = chars[i];
             }
             newChars[index++] = '#'; newChars[index++] = '$';
             return newChars;
             }
        }

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值