题解 | #最长回文子串#

最长回文子串

http://www.nowcoder.com/practice/b4525d1d84934cf280439aeecc36f4af

NC17 最长回文子串

区别:LC5是返回最大回文子串,NC17是返回最大回文子串长度,难度降低了

分析:

  • 暴力破解法
public class Solution1 {
    // 暴力破解法:求最长回文子串的长度
    public int getLongestPalindrome(String A, int n) {
        int len = A.length();
        if (len < 2) {
            return 1;
        }
        int maxLen = 1;
        char[] cs = A.toCharArray();
        for (int i = 0; i < len - 1; i++) {// 最后一个字符没必要枚举了
            for (int j = i + 1; j < len; j++) {
                // 最长回文串:长度>之前的max,且,是回文串
                if (j - i + 1 > maxLen && isPalindrome(cs, i, j)) {
                    maxLen = j - i + 1;
                }
            }
        }
        return maxLen;
    }

    private boolean isPalindrome(char[] cs, int i, int j) {
        while (i < j) {
            if (cs[i] != cs[j]) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }

}
  • 中心扩散法
public class Solution2 {
    // 中心扩散法:求最长回文子串的长度
    public int getLongestPalindrome(String A, int n) {
        if (n < 2) {
            return 1;
        }
        int maxLen = 1;
        char[] cs = A.toCharArray();
        for (int i = 0; i < n - 1; i++) {
            int len1 = getPalindromeCenterLen(cs, n, i, i);// 奇数中心的扩散长度
            int len2 = getPalindromeCenterLen(cs, n, i, i + 1);// 偶数中心的扩散长度
            len1 = Math.max(len1, len2);
            if (len1 > maxLen) {
                maxLen = len1;
            }
        }
        return maxLen;
    }


    private int getPalindromeCenterLen(char[] cs, int len, int left, int right) {
        int i = left;
        int j = right;
        while (i >= 0 && j < len) {
            if (cs[i] == cs[j]) {
                i--;
                j++;
            } else {
                break;
            }
        }
        // 循环跳出:cs[i]!=cs[j]
        // 此时的回文中心长度:j-i+1-2=j-i-1
        return j - i - 1;
    }
}
  • 动态规划法
public class Solution3 {
    // 动态规划法:求最长回文子串的长度
    public int getLongestPalindrome(String A, int n) {
        if (n < 2) {
            return 1;
        }
        int maxLen = 1;
        char[] cs = A.toCharArray();
        // dp[i][j]:表示s[i][j]是否是回文串
        boolean[][] dp = new boolean[n][n];
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 经验:dp区域是正方形的话,通常左下角区域无效不需要再填,因为走过的区域不用再走
        for (int j = 1; j <= n - 1; j++) { // 上三角区域,按列从上到下填
            for (int i = 0; i < j; i++) {
                // 首尾不相等时,必不是回文串
                if (cs[i] != cs[j]) {
                    dp[i][j] = false;
                } else {
                    // 首尾相等时,有2种情况
                    // 情况1:s[i...j]长度不超过3,不用检查必为回文串
                    // 情况2:s[i...j]长度大于3,由s[i+1...j-1]来判断
                    dp[i][j] = j - i + 1 <= 3 || dp[i + 1][j - 1];
                }
                // 更新max和begin
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                }
            }
        }
//        for (int i = n - 2; i >= 0; i--) {// 从右下往上填也行
//            for (int j = n - 1; j > i; j--) {
//            }
//        }
        return maxLen;
    }
}
  • Manacher法
public class Solution4 {
    // manachar法:求最长回文子串的长度
    public static int getLongestPalindrome(String A, int n) {
        if (A == null || n == 0) {
            return 0;
        }
        // 将s转换为加了特殊字符#的字符数组,目的是统一奇偶数的回文中心差异性问题
        // 比如:s=”cabac“转化为cs=[#c#a#b#a#c#]
        char[] cs = manacherString(A, n);
        // pArr[i]是cs[i]每个位置的最大回文半径
        // 比如:cs=[#c#a#b#a#c#],pArr=[1,2,1,2,1,6,1,2,1,2,1]
        int[] pArr = new int[cs.length];
        // pR是cs[i]每个位置的回文右边界的下一个位置
        // 比如:cs=[#c#a#b#a#c#],pR=1,3,3,5,5,11(此时pR第一次遍历完cs,之后的pR可以不再更新),11,11,11,11,11
        int pR = -1;
        // index是最近更新pR时的回文中心位置
        // 比如:cs=[#c#a#b#a#c#],index=0,1,1,3,3,5(之后pR不再更新,index也不再更新),5,5,5,5,5
        int index = -1;
        // max记录pArr[i]中最大值:pArr[i]最大值就能算出最长回文子串长度
        int maxLen = Integer.MIN_VALUE;
        for (int i = 0; i != cs.length; i++) {
            // 第一句代码:每轮循环时,i至少不需要验证的区域,先给到pArr[i],解释如下:
            // pR<=i:i超过了pR,无法优化,不用验证的区域就是pArr[i]本事=回文半径为1
            // pR>i:i没有超过pR,可以优化,至少不需要验的区域:Math.min(pArr[2 * index - i], pR - i)
            pArr[i] = pR > i ? Math.min(pArr[2 * index - i], pR - i) : 1;
            // 第二句代码:在i位置尝试往外扩最长回文半径长度pArr[i]:
            // 如果扩成功pArr[i]++;否则立刻停止扩的过程break
            while (i + pArr[i] < cs.length && i - pArr[i] >= 0) {
                if (cs[i + pArr[i]] == cs[i - pArr[i]])
                    pArr[i]++;
                else {
                    break;
                }
            }
            // 每轮循环,扩的长度超过回文右边界下一个位置,就更新pR和index
            if (i + pArr[i] > pR) {
                pR = i + pArr[i];
                index = i;
            }
            // 取pArr[i]中最大值
            maxLen = Math.max(maxLen, pArr[i]);
        }
        // 最长回文串长度是回文半径-1,比如#1#2#1#,2的最长回文半径是4,所以原始串121的长度是4-1=3
        return maxLen - 1;
    }

    // 将str转换成带#号的字符数组:解决奇数、偶数中心往外扩的差异性
    public static char[] manacherString(String s, int n) {
        char[] charArr = s.toCharArray();
        int index = 0;// index遍历charArr
        // s:a -> res:#a#,长度1 -> 3,奇数位放#,偶数位放原字符
        // s:ab -> res:#a#b#,长度2 -> 5,奇数位放#,偶数位放原字符
        // s:aba -> res:#a#b#a#,长度3 -> 7,奇数位放#,偶数位放原字符
        // 长度变化规律:len -> len+len+1=len*2+1,奇数位放#,偶数位放原字符
        char[] res = new char[n * 2 + 1];
        for (int i = 0; i != res.length; i++) {
            // 偶数位放#,奇数位放原字符
            res[i] = (i & 1) == 0 ? '#' : charArr[index++];
        }
        return res;
    }
}
全部评论

相关推荐

10-15 15:00
潍坊学院 golang
跨考小白:这又不是官方
投递拼多多集团-PDD等公司10个岗位
点赞 评论 收藏
分享
努力学习的小绵羊:我反倒觉得这种挺好的,给不到我想要的就别浪费大家时间了
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务