leecode5-最长回文子串

102 阅读1分钟

题目

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

来源:力扣(LeetCode)

链接:leetcode.cn/problems/lo…

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

动态规划

public class Leetcode5 {
    public String longestPalindrome(String s) {
        if (null == s || s.length() < 2) {
            return s;
        }
        int left = 0;
        // 长度至少为1
        int max = 1;
        int len = s.length();
        boolean dp[][] = new boolean[len][len];
        for (int i = 0; i < len; i++) {
            // 长度为1时都是回文串
            dp[i][i] = true;
        }
        // 遍历长度:从长度短的往长的演进
        for (int i = 2; i <= len; i++) {
            //遍历对应长度下的左指针
            for (int j = 0; j < len; j++) {
                int right = j + i - 1;
                if (right >= len) {
                    // 越界
                    continue;
                }
                //保证至少是(2,4)的距离
                boolean temp = s.charAt(j) == s.charAt(right);
                if (right - j <= 2) {
                    dp[j][right] = temp;
                } else {
                    dp[j][right] = dp[j + 1][right - 1] && temp;
                }
                if (dp[j][right] && right - j + 1 > max) {
                    max = right - j + 1;
                    left = j;
                }
            }
        }
        return s.substring(left, left + max);
    }
}

中心扩展

class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, end = 0;
        for (int i = 0; i < s.length(); i++) {
            int len1 = expandAroundCenter(s, i, i);
            int len2 = expandAroundCenter(s, i, i + 1);
            int len = Math.max(len1, len2);
            if (len > end - start) {
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);
    }
    
    public int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            --left;
            ++right;
        }
        return right - left - 1;
    }
}

解析

动态规划

  1. 为什么要用动态规划?

    题目是要求最大回文子串,而回文的定义就是左右对称;也就意味着,当我们把左右各自去掉一个字符后,还是回文子串;同时也意味着,当有一个回文子串后,在左右两边各加一个字符,如何两个字符一样,组成的新的子串也是回文子串。于是可以得出推导公式 boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j])

  2. 该题目动态规划的推导公式是什么?

    • 当长度为1时,肯定是回文,即dp[i][i]=true
    • 当长度<=3;即j-i<=2时,看两边的字符是否一样即可,即boolean[i][j] = (char[i]==char[j])
    • 其他情况:boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j])
  3. 个人觉得动态规划的难点:如何遍历,来达到以小求大的效果

    从咱们前面的推导公式来看, boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j]);其实本质就是把长度大的缩小长度小点来计算;所以第一层循环以长度来遍历,因为长度为1的结果可以预置,直接从len=2开始;而长度确定以后,下一步要确定左右边界,所以第二层以左边界遍历,左边界确定后,右边界则可以计算出;也就可以走后面的结果

中心扩展

这个方法就比较简单些,其实本质就是遍历每种回文串的可能中心点,然后从中心点一直往外扩散;知道遇到不是回文串为止,然后记录最大值即可

需要注意的一点就是,回文串的中心点可能是空,即偶数个字符;所以扩展时有两种公式,需要取最大的那个,即:

int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);

总结

  1. 中心扩展是这个题目比较适合也比较简单的方法,穷举所有的中心点,然后一直往外扩散
  2. 而这是也是动态规划的典型题目;类似的题目都是可以按照这种动态规划思维去做;主要是满足能从小推大,然后主要要确定推导公式和遍历目标