leetcode 5 最长回文子串问题

题目描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:

输入: "cbbd"
输出: "bb"

方法一暴力求解: 定义一个n*n维的数组flage,flage[i][j] 表示以i开始,以j结束的回文串的长度,如果Si,,,,,,Sj是回文串,那么flage[i][j]就是长度,否则值为0。然后取出flage数组中元素最大的值并返回相应的回文子串。此方法时间复杂度为O(n^3), 空间复杂度可以为O(1),(每次只保存最优结果数据,从而不需要用数组)---复杂度太大会超时

 /**
     * 暴力方法
     * 假设flage[i][j]表示以i开始,以j结束的回文串的长度。
     * 计算出所有以i开始以j结束回文串的长度大小,不是回文串的设置为0
     *
     * @param s
     * @return
     */
    public String longestPalindrome1(String s) {

        // a[i][j] 表示以i开始, 以j结束的串, 如果是回文串保存长度,不是直接为0
        int max = 1, x = 0, y = 0;
        int len = s.length();
        int[][] flage = new int[len][len];
        for (int i = 0; i < len; i++) flage[i][i] = 1;
        for (int i = 0; i < len; i++) {

            for (int j = i + 1; j < len; j++) {
                flage[i][j] = TheLengthOfStr(s.substring(i, j + 1));

                if (max < flage[i][j]) {
                    max = flage[i][j];
                    x = i;
                    y = j;
                }
            }
        }
        return s.substring(x, y + 1);
    }

方法二:动态规划 本质上是暴力方法的改进版本,

它利用数组P[i][j]保存Si到Sj是否是回文串,然后利用状态转移方程 P[i][j]={P[i+1][j-1]&&Si==Sj}, 初始化条件为P[i][i]=1, P[i][i+1]=Si==S(i+1)

扫描二维码关注公众号,回复: 10232610 查看本文章

先从找两个元素的回文串,再找三个元素的回文串,接着找四个元素的回文串,一直这样下去,直到k个元素的回文串。 时间复杂度为O(n^2), 空间复杂度为O(n^2) 

 public String longestPalindrome2(String s) {
        // 先初始化 一位
        char[] cSubstr = s.toCharArray();
        int maxValue = 0;
        int low = 0, high = 0;
        int len = s.length();
        boolean[][] P = new boolean[len][len];
        for (int i = 0; i < len; i++) {
            P[i][i] = true;

        }
        // 从2个元素到k个元素  P[j-k-1][j-1]
        boolean isFlage = false;
        for (int k = 1; k < len; k++) {
            isFlage = false;
            for (int j = k; j < len; j++) {
                P[j - k][j] = cSubstr[j - k] == cSubstr[j]&&(k==1||P[j-k+1][j-1]);
                if (P[j - k][j]&&!isFlage) {
                    maxValue = k;
                    low = j - k;
                    high = j;
                    isFlage = true;
                }
            }
        }
        return s.substring(low, high + 1);
    }

方法三:中心扩展法,其实就是枚举所有可能的中心点,从而来获得最大的回文串长度。 由于可能存在奇数或者偶数个回文串长度,所以中心点的选择需要考虑上述两种情况。

当回文串长度为偶数时,有n-1种可能的中心点;当回文串长度为奇数时,存在n种可能的中心点。也就是说总共存在2n-1种中心点情况。时间复杂度为O(n^2)

 public String longestPalindrome3(String s) {
        int len = s.length();
        if(len<=1) return s;
// 表示最优回文串对应的上下界, 界限包含在内
int start = 0, end = 0; for (int i = 0; i < len; i++) {
// 奇数情况
int len1 = theLengthOfSubStr(s, i, i, len);
// 偶数情况
int len2 = theLengthOfSubStr(s, i, i + 1, len); int le = Math.max(len1, len2); if (le > start - end) { // 如果新的回文串长度更长,修改回文串对应的上下界end, start start = i - (le-1 )/ 2; end = i + le / 2; } } return s.substring(start, end + 1); } public int theLengthOfSubStr(String s, int left, int right, int len) { int L = left, R = right; while (L >= 0 && R < len && s.charAt(L) == s.charAt(R)) { R++; L--; } return R - L - 1; }

方法四:马拉车方法Manacher,  此方法有点和KMP类似, 都是利用已知的一些信息来避免重复计算问题。此种方法时间复杂度为O(n),非常快。

该方法存在两个小技巧,第一个利用添加特殊字符的方法解决回文子串长度的奇偶数问题。第二点利用特殊字符防止下表越界。

假设原本输入数据为babad

第一个技巧是再每一元素的左右两边都加入'#'字符。假设原来数据长度为n, 则‘#’字符出现的个数为n+1, 从而总的数据长度变为奇数2n+1。

    #b#a#b#a#d

第二个技巧实在边界两端添加特殊字符,防止小标越界(代码中会讨论)

?#b#a#b#a#d\0

一些定义及概念

cArray表示字符串数组, p[i]表示以元素cArray[i]为对称中心的最大回文串的半径。 id表示当前最大长度的回文串对应的对称中心,mx当前最大回文串对应的右边界(不包含边界),其左边界为ml。j=2*id-i 表示i以id为对称中心的情况下对应的对称点。我们直到当i<mx时,i对应对称点j的p[j]我们是知道的,从而我们可以利用已知的p[j]来求相应的p[i],j的左边界为jl, 右边界为jr,这样会避免重复计算。

我们依据条件不同分为三种情况来计算相应的p[j]

第一种情况, i<mx, j 的回文串有一部分在 id 的之外。 此种情况下i不能继续扩展, p[i]=p[j=2*id-i]

第二种情况 i<mx, j的回文串有一部分在 id 的之内。 此种情况下i不能继续扩展, p[i]=p[j=2*id-i]

第三种情况i<mx, j 回文串左端正好与 id 的回文串左端重合,此时p[i] = p[j]p[i] = mx - i,并且p[i]还可以继续增加。

具体实现代码如下

/**
     * 利用马拉车方法Menacher
     *
     * @param s 目标字符串
     * @return
     */
    public String longestPalindrome4(String s) {

        int len = s.length();
        if(len<=1) return s;
        len = 2*len + 3;
        char cArray[] = new char[len];
        cArray[0] = '$';
        cArray[1] = '#';
        cArray[len-1] = '\n';
        for (int i = 2, j=0; i <len-1; i=i+2) {
            cArray[i]=s.charAt(j++);
            cArray[i + 1] = '#';
        }
        // p[i]保存以点cArray[i]为对称中心的最大半径长度
        int[] p = new int[len];
        // 表示当前最大半径的中心所在位置, mx当前最大半径所在的有边界, maxLen最大的回文串长度
        int id=0, mx = 0, maxLen = -1, medium=-1;
        for (int i = 1; i < len-1; i++) {
            if(i<mx){//可以借助对称性来求p[i]
                p[i] = Math.min(p[2 * id - i], mx - i);
            }else
                p[i] = 1; // 长度为1
              // 不用判断越界,因为左右两边分别添加'$'和'\0'防止溢出
            while (cArray[i - p[i]] == cArray[i + p[i]]) {
                p[i]++;
            }
            // 更新当前最远右边界
            if (i + p[i] > mx) {
                mx = i + p[i];
                id = i;
            }
            System.out.println("i = " + i + "  p[i] = " + p[i] + "");
             // p[i] - 1 当前以i为中心的回文串的长度(去掉#)
            if (p[i] - 1 > maxLen) {
                maxLen = p[i] - 1;
                medium = i - maxLen;
            }

        }
        // 表示当前最优回文串的起始位置
        medium = medium / 2;

        return s.substring(medium, medium + maxLen);
    }

猜你喜欢

转载自www.cnblogs.com/09120912zhang/p/12585071.html