5. 最长回文子串 Longest Palindromic Substring

Given a string s, return the longest palindromic substring in s.

Input: s = "babad"
Output: "bab"
Note: "aba" is also a valid answer.

 

方法一:

暴力法,双层循环,验证所以字串是否是回文,找到最长的。

时间复杂度O(n^3)

 

方法二:中心扩展算法

回文是由中心往外扩展的,对于n维数组,有2n-1个中心

因为一种回文是奇数长度,只有一个中心点,数组有n个中心

一种回文是偶数长,有两个中心点,数组有n-1个中心。

 

    public String longestPalindrome(String s){
        int maxLen = 0;
        String dest = "";
        for(int i = 0; i < s.length(); i++){
            int len1 = maxLenth(s,i,i);
            int len2 = maxLenth(s, i, i + 1);
            int len = Math.max(len1,len2);
            if (len > maxLen ){
                dest = s.substring(i-(len-1)/2,i+len/2+1);
                maxLen = len;
            }
        }
        return dest;
    }

    private int maxLenth(String s, int i, int j){
        int L = i, R = j;
        while(L >=0 && R < s.length() && s.charAt(L)==s.charAt(R)){
            L--;
            R++;
        }
        return R - L -1;
    }

时间复杂度为O(n^2)

 

方法三:动态规划

对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。

状态转移方程为:

P(i,j)=P(i+1,j1)(Si==Sj


P(i,j)P(i,j) 表示字符串 ss 的第 ii 到 jj 个字母组成的串是否为回文字串。 

public String longestPalindromeDP(String s){
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        String dst = "";
        for( int l  = 0; l < s.length(); ++l){
            for ( int i = 0; i + l < s.length(); ++i){
                int j = i + l;
                if (l == 0){
                    dp[i][j] = true;
                }else if(l == 1){
                    dp[i][j] = (s.charAt(i) == s.charAt(j));
                }else{
                    dp[i][j] = (s.charAt(i) == s.charAt(j) )&& dp[i + 1][j - 1];
                }
                if (dp[i][j] && (l + 1 > dst.length())){
                    dst = s.substring(i, i + l + 1);
                }

            }

        }
        return dst;
    }

时间复杂度为O(n^2)

 

方法四:Manacher 算法

对于长度为奇数的回文字符串,在中心扩展算法的过程中,我们能够得出每个位置的臂长。那么当我们要得出以下一个位置 i 的臂长时,可以利用之前得到的信息

具体来说,如果位置 j (在i前面)的臂长为 length,并且有 j + length > i:

当在位置 i 开始进行中心拓展时,我们可以先找到 i 关于 j 的对称点 2 * j - i。那么如果点 2 * j - i 的臂长等于 n,我们就可以知道,点 i 的臂长至少为 min(j + length - i, n)。那么我们就可以直接跳过 i 到 i + min(j + length - i, n) 这部分,从 i + min(j + length - i, n) + 1 开始拓展。

我们只需要在中心扩展法的过程中记录右臂在最右边的回文字符串,将其中心作为 j,在计算过程中就能最大限度地避免重复计算。

 

对于回文长度为偶数的情况:我们向字符串的头尾以及每两个字符中间添加一个特殊字符 #,比如字符串 aaba 处理后会变成 #a#a#b#a#。那么原先长度为偶数的回文字符串 aa 会变成长度为奇数的回文字符串 #a#a#,而长度为奇数的回文字符串 aba 会变成长度仍然为奇数的回文字符串 #a#b#a#,所以我们就不需要再考虑长度为偶数的回文字符串

 

public String longestPalindromeManacher(String s){

        int len = s.length();
        if (len < 2) {
            return s;
        }
        int start = 0, end = -1;
        s = addBoundaries(s, '#');
        List<Integer> arm_len = new ArrayList<Integer>();

        int right = -1, j = -1;
        for (int i = 0; i < s.length(); ++i) {
            int cur_arm_len;
            if (right >= i) {
                int i_sym = j * 2 - i;
                int min_arm_len = Math.min(arm_len.get(i_sym), right - i);
                cur_arm_len = maxLenth(s, i - min_arm_len, i + min_arm_len)/2;
            } else {
                cur_arm_len = maxLenth(s, i, i)/2;
            }
            arm_len.add(cur_arm_len);
            if (i + cur_arm_len > right) {
                j = i;
                right = i + cur_arm_len;
            }
            if (cur_arm_len * 2 + 1 > end - start) {
                start = i - cur_arm_len;
                end = i + cur_arm_len;
            }
        }

        StringBuffer ans = new StringBuffer();
        for (int i = start; i <= end; ++i) {
            if (s.charAt(i) != '#') {
                ans.append(s.charAt(i));
            }
        }
        return ans.toString();


    }

    private String addBoundaries(String s, char divide) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            stringBuilder.append(divide);
            stringBuilder.append(s.charAt(i));
        }
        stringBuilder.append(divide);
        return stringBuilder.toString();
    }
    private int maxLenth(String s, int i, int j){
        int L = i, R = j;
        while(L >=0 && R < s.length() && s.charAt(L)==s.charAt(R)){
            L--;
            R++;
        }
        return R - L -1;
    }

时间复杂度为O(n)

 

参考链接:

https://leetcode-cn.com/problems/longest-palindromic-substring

https://leetcode.com/problems/longest-palindromic-substring

 

posted @ 2020-11-20 16:40  diameter  阅读(101)  评论(0编辑  收藏  举报