5. 最长回文子串

556 阅读2分钟

题目介绍

力扣5题:leetcode-cn.com/problems/lo…

image.png

方法一:动态规划

对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab” 是回文串,那么 “ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。

根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串。

那么我们就可以写出动态规划的状态转移方程:

image.png

代码如下:

public String longestPalindrome(String s) {
    if (s == null || s.length() < 2) {
        return s;
    }
    int strLen = s.length();
    int maxStart = 0;  //最长回文串的起点
    int maxEnd = 0;    //最长回文串的终点
    int maxLen = 1;  //最长回文串的长度

    boolean[][] dp = new boolean[strLen][strLen];

    for (int r = 1; r < strLen; r++) {
        for (int l = 0; l < r; l++) {
            //r - l <= 2 表示两者之间只有一个字符
            if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
                dp[l][r] = true;
                if (r - l + 1 > maxLen) {
                    //更新最大值
                    maxLen = r - l + 1;
                    maxStart = l;
                    maxEnd = r;

                }
            }

        }

    }
    return s.substring(maxStart, maxEnd + 1);
}

方法二:中心扩散法

对于这个问题,我们首先应该思考的是,给一个字符串s,如何在s中找到一个回文子串?

有一个很有趣的思路:既然回文串是一个正着反着读都一样的字符串,那么如果我们把s反转,称为s',然后在ss'中寻找最长公共子串,这样应该就能找到最长回文子串。

比如说字符串abacd,反过来是dcaba,它俩的最长公共子串是aba,也就是最长回文子串。

但是这个思路是错误的,比如说字符串aacxycaa,反转之后是aacyxcaa,最长公共子串是aac,但是最长回文子串应该是aa

虽然这个思路不正确,但是这种把问题转化为其他形式的思考方式是非常值得提倡的

下面,就来说一下正确的思路,如何使用双指针。

寻找回文串的问题核心思想是:从中间开始向两边扩散来判断回文串。对于最长回文子串,就是这个意思:

for 0 <= i < len(s):
    找到以 s[i] 为中心的回文串
    更新答案

但是呢,我们刚才也说了,回文串的长度可能是奇数也可能是偶数,如果是abba这种情况,没有一个中心字符,上面的算法就没辙了。所以我们可以修改一下:

for 0 <= i < len(s):
    找到以 s[i] 为中心的回文串
    找到以 s[i] 和 s[i+1] 为中心的回文串
    更新答案

PS:读者可能发现这里的索引会越界,等会会处理。

按照上面的思路,先要实现一个函数来寻找最长回文串,这个函数是有点技巧的:

public String palindrome(String s ,int l ,int r) {
    //防止越界
    while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
        //向两边扩展
        l--;
        r++;
    }
    return s.substring(l + 1, r);
}

为什么要传入两个指针lr呢?因为这样实现可以同时处理回文串长度为奇数和偶数的情况

for 0 <= i < len(s):
    # 找到以 s[i] 为中心的回文串
    palindrome(s, i, i)
    # 找到以 s[i] 和 s[i+1] 为中心的回文串
    palindrome(s, i, i + 1)
    更新答案

完整代码如下:

class Solution {
    public String longestPalindrome(String s) {
        String result = "";
        for(int i = 0 ; i < s.length() ; i++) {
            String s1 = palindrome(s, i,i);
            String s2 = palindrome(s, i,i + 1);
            //更新最大值
            result = result.length() > s1.length() ? result : s1;
            //更新最大值
            result = result.length() > s2.length() ? result : s2;
        }
        return result;
    }

    /**
     * 中心扩展法
     */
    public String palindrome(String s ,int l ,int r) {
        //防止越界
        while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
            //向两边扩展
            l--;
            r++;
        }
        return s.substring(l + 1, r);
    }
}

至此,这道最长回文子串的问题就解决了,时间复杂度 O(N^2),空间复杂度 O(1)。

值得一提的是,这个问题可以用动态规划方法解决,时间复杂度一样,但是空间复杂度至少要 O(N^2) 来存储 DP table。这道题是少有的动态规划非最优解法的问题。