LeetCode5---最长回文子串

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

示例 1:

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

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

解法1 动态规划

对于回文子串,会有这么一个性质,假设字符串s的s[i]-s[j] (j>i)为回文子串,那么s[i+1]-s[j-1]也必定是回文串。那么根据这个性质我们就能将问题分解为更小的部分,如果判断s[i-j]是不是回文子串,只需要判断s[i]==s[j] 以及s[i-j]是不是回文子串。如果我们从长度为1和2的子串开始判断,然后后面更长的子串就可以依据前面的计算结果更快的判断出来。
时间复杂度:O(n^2)
空间复杂度:O(n^2)
实现如下:

/**
     * dp解法:
     * dpMap[][]用于存储回文串信息  dp[i][j]表示 s[j]到s[i]为回文串
     * 初始化: dp[i][i] = true 如果s[i]==s[i+1] 则dpMap[i+1][i]=true
     * 规则:如果s[i] == s[j] && dpMap[i-1][j+1]=true 则 dpMap[i][j] = true
     *
     * @param s 字符串
     * @return 最长回文串
     */
    public String longestPalindrome(String s) {
        //存储回文串 dpMap[i][j]=true 表示 s[j]到s[i]为回文串
        boolean dpMap[][] = new boolean[s.length()][s.length()];
        //存取最长回文串起点
        int start = 0;
        //存取最长回文串长度
        int maxLength = 1;

        //初始化dpMap  处理里单个字符和两个字符的情况
        for (int i = 0; i < s.length(); i++) {
            dpMap[i][i] = true;
            if (i + 1 < s.length() && s.charAt(i) == s.charAt(i + 1)) {
                dpMap[i + 1][i] = true;
                start = i;
                maxLength = 2;
            }
        }

        //  dpMap[i-1][j+1] == true && s[i] == s[j]    ==> dpMap[i][j] = true
        for (int i = 2; i < s.length(); i++) {
            for (int j = 0; j < i - 1; j++) {
                if (dpMap[i - 1][j + 1] && s.charAt(i) == s.charAt(j)) {
                    dpMap[i][j] = true;
                    if (i - j + 1 > maxLength) {
                        maxLength = i - j + 1;
                        start = j;
                    }
                }
            }
        }

        return s.substring(start, start + maxLength);
    }

解法2 中心拓展法

选定字符串的一个字符,依次向两边逐渐拓展,每次位移长度为1,如果两边选取的字符相同,那么是回文串,继续拓展,否则终止,选取下一个字符。需要注意的是分aba、abba两种形式的情况。
时间复杂度:O(n^2)
空间复杂度:O(1)
这个解法比上个速度更慢,但是空间复杂度较低
解法如下:

private String getChildStr(String s, int pre, int aft) {
        int len = s.length();
        while (pre >= 0 && aft < len && s.charAt(pre) == s.charAt(aft)) {
            pre--;
            aft++;
        }

        return s.substring(pre + 1, aft);
    }

    /**
     * 中心拓展法:
     * 选定一个字符,往两边拓展更新最长子串
     * 需要注意 aba  abba这两种形式的情况
     *
     * @param s 字符串
     * @return 最长回文串
     */
    public String longestPalindrome1(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        String rs = "";
        for (int i = 1; i < len; i++) {
            //判断aba情况
            String temp = getChildStr(s, i, i);
            if (temp.length() > rs.length()) {
                rs = temp;
            }
            //判断abba情况
            temp = getChildStr(s, i - 1, i);
            if (temp.length() > rs.length()) {
                rs = temp;
            }
        }
        return rs;
    }

解法3 Manacher算法

具体算法原理参考:https://segmentfault.com/a/1190000008484167
上面博客在讲述为何p[i] = min(p[2 * id - i], mx - i)时候讲的不是那么容易理解。
对于第一种情况:
如果a部分超出mx,根据回文串性质,则a=b。此时根据回文串性质又可以推出b=c,若p[i]取值能超出mx的话,根据回文串性质c=d,此时可以接着推导出a=d,那么这时候p[id]的回文串长度为mx就不成立了。所以不存在这种情况。
对于第二种情况:
j完全在内部的时候,如果取值超过j的回文串长度,那么根据回文串性质,与第一种情况分析相似,p[j]的长度就不成立,所以也不存在。
对于第三种情况:
刚好是mx的情况,从mx再向外拓展不会受之前结果的影响。

实现如下:

/**
     * Manacher算法
     * @param s 字符串
     * @return 最长回文子串
     */
    public String longestPalindrome2(String s) {
        //字符串预处理  aaa ==> $a#a#a#
        StringBuilder tempStr = new StringBuilder("$");
        for (int i = 0; i < s.length(); ++i) {
            tempStr.append(s.charAt(i)).append("#");
        }
        tempStr.append("\0");
        s = tempStr.toString();

        //用StringBuilder比较好   这里偷懒写法
//        s = Arrays.stream(s.split("")).reduce("$", (pre, next)->pre+next+"#") + "\0";

        int[] dp = new int[s.length()];

        int maxLength = 1;
        int start = 1;
        int id = 0;
        int mx = 0;

        int sLen = s.length() - 1;
        for (int i = 1; i < sLen; ++i) {
            //如果在mx(在回文串内或者刚好在边界)范围内  取dp[i] = Math.min(dp[2 * id - i], mx - i) 否则dp[i]=1
            if (i < mx) {
                dp[i] = Math.min(dp[2 * id - i], mx - i);
            } else {
                dp[i] = 1;
            }

            //刚好在边界  或者边界以外 需要往外扩张
            while (s.charAt(i - dp[i]) == s.charAt(i + dp[i])) {
                dp[i]++;
            }

            //如果mx最大值右移了  更新mx和id
            if (mx < i + dp[i]) {
                id = i;
                mx = i + dp[i];
            }

            //更新最长回文串
            if (maxLength < dp[i]) {
                maxLength = dp[i];
                start = i;
            }
        }
        return s.substring(start - maxLength + 1, start + maxLength).replaceAll("$|#", "");
    }

你可能感兴趣的:(LeetCode)