最长回文子串解法

最长回文子串

原文链接:https://www.leahy.club/archives/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2

1. 题目

在这里插入图片描述

2. 解答

常见的解法主要有:

暴力法:列举出所有可能的子串,并对每个子串进行分析。

最长公共子串法:根据回文的定义,正反读是一样的。那么可以将原理来的字符串倒置,从而将问题转化成了求解最长公共子串的问题。求解最长公共子串可以使用动态规划。

动态规划的状态转移方程:

a r r [ i ] [ j ] = a r r [ i − 1 ] [ j − 1 ] + 1 arr[i][j]=arr[i-1][j-1]+1 arr[i][j]=arr[i1][j1]+1

a r r [ i ] [ j ] arr[i][j] arr[i][j]保存的是公共子串的长度。

暴力法的优化:暴力法中存在很多重复判断,这里采用空间换时间的做法,存储中间结果换取低时间复杂度。

首先定义 p ( i , j ) p(i,j) p(i,j),

p ( i , j ) = { t r u e s[i,j]是回文串 f a l s e s[i,j]不是回文串 p(i,j)= \begin{cases} true& \text{s[i,j]是回文串}\\ false& \text{s[i,j]不是回文串} \end{cases} p(i,j)={truefalses[i,j]是回文串s[i,j]不是回文串

接下来有: p ( i , j ) = p ( i + 1 , j − 1 ) & & ( s [ i ] = = s [ j ] ) p(i,j)=p(i+1,j-1) \&\& (s[i]==s[j]) p(i,j)=p(i+1,j1)&&(s[i]==s[j])

中心扩展算法:

回文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。

Manacher算法(马拉车)算法:

马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性。

详见:马拉车算法

3. 代码
3.1 暴力法

时间复杂度: O ( n 3 ) O(n^{3}) O(n3);空间复杂度: O ( 1 ) O(1) O(1)

//1.暴力法
    public static String solution(String s) {

        if (s == null)
            return null;

        int len = s.length();
        String res = s.substring(0,1);

        for(int i = 0; i < len; i++) {
            for (int j = i+1; j <= len; j++) {
                String subStr = s.substring(i,j);
                if (isPalindrome(subStr))
                    res = (subStr.length() > res.length()) ? subStr : res;
            }
        }
        return res;
    }

private static boolean isPalindrome(String s) {
        int n = s.length();

        for (int i = 0; i < n / 2; i++) {
            if (s.charAt(i) != s.charAt(n - i - 1))
                return false;
        }
        return true;
    }
3.2 最长相同子串法

时间复杂度: O ( n 2 ) O(n^{2}) O(n2);空间复杂度: O ( n ) O(n) O(n).

//2.最长相同子串法
    public static String solution2(String s) {

        if (s == null)
            return null;

        int len = s.length();
        String origin = s;
        String reverse = new StringBuilder(s).reverse().toString();
        int[][] arr = new int[len][len];
        int maxLen = 0;
        int maxEnd = 0;

        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                if (origin.charAt(i) == reverse.charAt(j)) {
                    if (i == 0 || j == 0)
                        arr[i][j] = 1;
                    else
                        arr[i][j] = arr[i-1][j-1] + 1;
                }

                if (arr[i][j] > maxLen) {
                    int beforeRev = len - j - 1;
                    if (beforeRev + arr[i][j] - 1 == i) {
                        maxLen = arr[i][j];
                        maxEnd = i;
                    }
                }
            }
        }
        return s.substring(maxEnd - maxLen + 1, maxEnd + 1);
    }


    //3.最长子串优化版
    public static String solution3(String s) {

        if (s == null || s.length() == 0)
            return null;

        int len = s.length();
        String origin = s;
        String reverse = new StringBuilder(s).reverse().toString();
        int[] arr = new int[len];
        int maxLen = 0;
        int maxEnd = 0;

        for (int i = 0; i < len; i++) {
            for (int j = len - 1; j >= 0; j--) {
                if (origin.charAt(i) == reverse.charAt(j)) {
                    if (i == 0 || j == 0)
                        arr[j] = 1;
                    else
                        arr[j] = arr[j-1] + 1;
                }
                else
                    arr[j] = 0; //之前使用二维数组,不需要置零,现在需要。

                if (arr[j] > maxLen) {
                    int beforeRev = len - j - 1;
                    if (beforeRev + arr[j] - 1 == i) {
                        maxLen = arr[j];
                        maxEnd = i;
                    }
                }
            }
        }
        return s.substring(maxEnd - maxLen + 1, maxEnd + 1);
    }
3.3 暴力法的优化(采用动态规划)

时间复杂度: O ( n 2 ) O(n^{2}) O(n2);空间复杂度: O ( n ) O(n) O(n)

//4.暴力法的优化(采用动态规划)
    public static String solution4(String s) {
        if (s == null || s.length() == 0)
            return s;

        int len = s.length();
        String res = s.substring(0,1);
        boolean[][] p = new boolean[len][len];

        for (int i = 1; i <= len; i++) { //i表示考察的字符串的长度
            for (int start = 0; start < len; start++) {
                int end = start + i - 1;
                //超出索引范围
                if (end >= len)
                    break;
                //单独判断长度为1和2的情况
                if (i == 1 || (i == 2 && s.charAt(start) == s.charAt(end)))
                    p[start][end] = true;

                else
                    p[start][end] = (s.charAt(start) == s.charAt(end)) && p[start+1][end-1];

                if (p[start][end] == true)
                    res = (end - start + 1 > res.length()) ? s.substring(start, end + 1) : res;
            }
        }
        return res;
    }

    //5.暴力解法的优化(动态规划写法二)
    public static String solution5(String s) {
        if (s == null || s.length() == 0)
            return s;

        int len = s.length();
        String res = "";
        boolean[][] p = new boolean[len][len];

        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                p[i][j] = (s.charAt(i) == s.charAt(j)) && (j - i < 2 || p[i+1][j-1]); //j-i < 2表示长度为1和2的情况
                if (p[i][j] == true)
                    res = (j - i + 1 > res.length()) ? s.substring(i, j + 1) : res;
            }
        }
        return res;
    }

    //6.暴力解法的优化(动态规划写法二)的优化
    public static String solution6(String s) {
        if (s == null || s.length() == 0)
            return s;

        int len = s.length();
        String res = "";
        boolean[] p = new boolean[len];

        for (int i = len - 1; i >= 0; i--) {
            for (int j = len - 1; j >= i; j--) {
                p[j] = (s.charAt(i) == s.charAt(j)) && (j - i < 2 || p[j-1]); //j-i < 2表示长度为1和2的情况
                if (p[j] == true)
                    res = (j - i + 1 > res.length()) ? s.substring(i, j + 1) : res;
            }
        }
        return res;
    }
3.4 中心扩展算法

时间复杂度: O ( n 2 ) O(n^{2}) O(n2);空间复杂度: O ( 1 ) O(1) O(1)

//7.中心扩展算法
    public static String solution7(String s) {
        if (s == null || s.length() <= 1)
            return s;
        int len = s.length();
        int start = 0;
        int end = 0;

        for (int i = 0; i < len; i++) {
            int len1 = expandAroundCenter(s, i,i);
            int len2 = expandAroundCenter(s, i, i+1);
            int maxLen = Math.max(len1, len2);
            if (end - start < maxLen) {
                start = i - (maxLen-1)/2;
                end = i + maxLen/2;
            }
        }
        return s.substring(start, end+1);
    }

    private static int expandAroundCenter(String s, int left, int right) {
        int p1 = left, p2 = right;
        while (p1 >= 0 && p2 < s.length() && s.charAt(p1) == s.charAt(p2)) {
            p1--;
            p2++;
        }
        return p2 - p1 - 1;
    }
3.5 Manacher算法

时间复杂度: O ( n ) O(n) O(n);空间复杂度: O ( n ) O(n) O(n)

//8.马拉车算法
    public static String solution8(String s) {
        if (s == null || s.length() <= 1)
            return s;

        //第一步预处理
        String t = "$";
        for (int i = 0; i < s.length(); i++)
            t += "#" + s.charAt(i);
        t += "#@";

        //第二步计算数组p、起始索引、最长回文半径
        int n = t.length();
        int[] p = new int[n];
        int id = 0, mx = 0; //id能够延伸到最右端的那个回文子串的中心点位置;mx表示id对于的最右端位置
        //最长回文子串长度
        int maxLen = -1;
        //最长回文子串的中心位置索引
        int index = 0;
        for (int j = 1; j < n - 1; j++) {

            //最关键的逻辑:当回文串能够延伸到的最右端位置mx大于j时,
            //取与j对称的点p[2*id-j]的回文串半径与mx-j二者中较小的
            //作为p[j]的临时值,之后向左右两边进行扩展,如果扩展成
            //功则需要更新mx和id的值
            p[j] = mx > j ? Math.min(p[2*id-j], mx-j) : 1;

            //向左右两边扩展,扩展右边界
            while (t.charAt(j+p[j]) == t.charAt(j-p[j]))
                p[j]++;
            //如果回文子串的右边界超过了mx,则需要更新mx和id的值
            if (mx < p[j] + j) {
                mx = p[j] + j;
                id = j;
            }
            //如果回文子串的长度大于maxLength,则更新maxLen和index的值
            if (maxLen < p[j] - 1) {
                maxLen = p[j] - 1;
                index = j;
            }
        }

        //第三步截取字符串,输出结果
        int start = (index-maxLen)/2;
        return s.substring(start, start + maxLen);
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中的代码展示了一种求解最长回文子串的暴力解法,即遍历字符串的所有子串,判断是否为回文,并记录最长的回文子串。但是这种方法的时间复杂度较高,不适用于较长的字符串。 Python 中有一种更优化的方法来求解最长回文子串,即中心扩展法。该方法的基本思想是从字符串的每个字符和每两个相邻字符之间展开,向两边扩展判断是否为回文子串。具体步骤如下: 1. 定义一个函数 expandAroundCenter,用于判断以某个中心点向两边扩展的回文子串的长度。 2. 遍历字符串,将每个字符和其相邻字符都作为中心点进行扩展,计算得到回文子串的最大长度。 3. 根据最大长度和中心点位置,确定最长回文子串的起始位置和结束位置。 4. 返回最长回文子串。 下面是基于中心扩展法的 Python 代码示例: ``` class Solution: def longestPalindrome(self, s): if len(s) < 2: return s start, end = 0, 0 for i in range(len(s)): len1 = self.expandAroundCenter(s, i, i) # 以一个字符为中心向两边扩展 len2 = self.expandAroundCenter(s, i, i+1) # 以相邻两个字符为中心向两边扩展 max_len = max(len1, len2) if max_len > end - start: start = i - (max_len - 1) // 2 end = i + max_len // 2 return s[start:end+1] def expandAroundCenter(self, s, left, right): while left >= 0 and right < len(s) and s[left] == s[right]: left -= 1 right += 1 return right - left - 1 s = "ac" S = Solution() result = S.longestPalindrome(s) print(result) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值