leetcode-5-最长回文子串


本题是leetcode,地址:5. 最长回文子串

题目

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

示例 1:

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

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

分析

题目还是很好理解的,思路有大致三种,一种暴力解、一种动态规划、一种是中心扩散法;

暴力解决分析:根据回文子串的定义,枚举所有长度大于等于 2的子串,依次判断它们是否是回文,在枚举的过程中需要记录“当前子串的起始位置”和“子串长度”,最后通过substring截取一下对应的字符串就好了。很容发现,这种解法效率很低!时间复杂度:O(N^3),N是字符串的长度,枚举字符串的左边界、右边界,然后继续验证子串是否是回文子串,这三种操作都与 NN 相关;

code-暴力解决

   public boolean check(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

   public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, maxLenght = 1;
        char[] chars = s.toCharArray();
        for (int left = 0; left < chars.length; left++) {
            for (int right = chars.length - 1; right > left; right--) {
                boolean check = check(s, left, right);
                if (check && right - left + 1 > maxLenght) {
                    start = left;
                    maxLenght = right - left + 1;
                    break;
                }
            }
        }
        return s.substring(start, start + maxLenght);
    }

code-动态规划

在设计动态规划时,需要考虑的点如下:

思考状态
思考状态转移方程
思考初始化
思考输出
思考优化空间

回文字符串的特点:

如果一个字符串的头尾两个字符都不相等,那么这个字符串一定不是回文串;
如果一个字符串的头尾两个字符相等,才有必要继续判断下去。
如果里面的子串是回文,整体就是回文串;
如果里面的子串不是回文串,整体就不是回文串。

如果dp[i] [j] 表示子串 s[i..j] 是否为回文子串,这里子串 s[i..j] 定义为左闭右闭区间,可以取到 s[i]s[j]

  • 状态转换公式:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]

边界条件:表达式 [i + 1, j - 1] 不构成区间,即长度严格小于 2,即 j - 1 - (i + 1) + 1 < 2 ,即 j - i < 3。怎么理解?即s[i..j] 的长度等于 2 或者等于 3 的时候,其实只需要判断一下头尾两个字符是否相等就可以直接下结论,而不需要参考之前的结果;

  • 初始化:单个字符一定是回文串,因此把对角线先初始化为 true,即 dp[i][i] = true
  • 输出:dp[i][j] = true,记录子串的长度和起始位置,当然我们是找最长的,需要跟上次记录的做比较;

时间复杂度:O(N^2)

根据以上信息就可以写我们的代码了


    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int begin = 0, maxLen = 1;
        // 状态记录
        boolean[][] dp = new  boolean[len][len];
        // 初始化:单个字符一定是回文串,因此把对角线先初始化为 `true`,即 `dp[i][i] = true` 
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        for (int j = 1; j < len ; j ++) {
            for (int i = 0; i < len; i ++) {
                // 判断是否相等
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    // 边界条件:即`s[i..j]` 的长度等于 `2` 或者等于 `3`
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        // 状态转换公式:dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                // 记录begin和长度
                if (dp[i][j] && j - i + 1 > maxLen) {
                    begin = i;
                    maxLen = j - i + 1;
                }
            }
        }

        return s.substring(begin, begin + maxLen);
    }

code-中心扩散法

暴力法采用双指针两边夹,验证是否是回文子串。如果枚举可能出现的回文子串的“中心位置”,从“中心位置”尝试尽可能扩散出去,得到一个回文串。遍历每一个索引,以这个索引为中心,利用“回文串”中心对称的特点,往两边扩散,看最多能扩散多远。枚举“中心位置”时间复杂度为 O(N),从“中心位置”扩散得到“回文子串”的时间复杂度为 O(N),时间复杂度可以降到 O(N^2)。

  • 奇数回文串的“中心”是一个具体的字符,例如:回文串 "aba" 的中心是字符 "b";
  • 偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 "abba" 的中心是两个 "b" 中间的那个“空隙”。

image-20200718165946298

    public String longestPalindrome(String s) {

        int len = s.length();
        if (len < 2) {
            return s;
        }
        String res = "";
        int maxLength = 0;
        for (int i = 0; i < len; i++) {
            String one = around(s, i, i);
            String two = around(s, i, i + 1);
            String maxString = one.length() > two.length() ? one : two;
            if (maxString.length() > maxLength) {
                res = maxString;
                maxLength = res.length();
            }
        }
        return res;
    }

    public String around(String s, int left, int right) {

        while (left >= 0 && right < s.length()) {
            if (s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            } else {
                break;
            }
        }
        return s.substring(left + 1, right);
    }

你的鼓励也是我创作的动力

打赏地址

posted @ 2020-07-18 16:31  Yangsc_o  阅读(201)  评论(0)    收藏  举报