LeetCode_动态规划_中等_5.最长回文子串

1.题目

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

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

示例 2:
输入:s = “cbbd”
输出:“bb”

示例 3:
输入:s = “a”
输出:“a”

示例 4:
输入:s = “ac”
输出:“a”

提示:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成
“回文串”是一个正读和反读都一样的字符串,比如 “level” 或者 “noon” 等等就是回文串

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring

2.思路

(1)暴力穷举法
先编写一个判断字符串是否为回文串的方法,然后再使用两层 for 循环,用该方法对字符串 s 的每一个子串进行判断,若该子串是回文串,则保存该子串本身以及其长度,并且与上一个结果进行比较(如果存在),取它们之间长度更大的一个即可。此外,当 s 剩余子串的长度小于当前已找到的回文子串的长度时,直接返回结果即可,这样可以提高效率。

(2)动态规划
思路参考本题官方题解

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

② 根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 dp[i][j] 表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i : j])是否为回文串,具体如下所示:

d p [ i ] [ j ] = { t r u e , 如果子串 s [ i : j ] 是回文串 f a l s e , 其它情况 dp[i][j] = \begin{cases} true, 如果子串 s[i : j] 是回文串\\ false, 其它情况\end{cases} dp[i][j]={true,如果子串s[i:j]是回文串false,其它情况

这里的「其它情况」包含两种可能性:

  • s[i : j] 本身不是一个回文串;
  • i > j,此时 s[i, j] 本身不合法。

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

dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j])

也就是说,只有 s[i + 1 : j - 1]是回文串,并且 s 的第 i 和 j 个字母相同时,s[i : j] 才会是回文串。

④ 上文的所有讨论是建立在子串长度大于 2 的前提之上的,我们还需要考虑动态规划中的边界条件,即子串的长度为 1 或 2。对于长度为 1 的子串,它显然是个回文串;对于长度为 2 的子串,只要它的两个字母相同,它就是一个回文串。因此我们就可以写出动态规划的边界条件:

{ d p [ i ] [ j ] = t r u e d p [ i ] [ i + 1 ] = ( s [ i ] = = s [ i + 1 ] ) \begin{cases} dp[i][j] = true\\ dp[i][i + 1] = (s[i] == s[i + 1])\end{cases} {dp[i][j]=truedp[i][i+1]=(s[i]==s[i+1])

⑤ 根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 dp[i][j] = true 中 j - i + 1(即子串长度)的最大值。注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

(3)中心扩展法
遍历整个字符串,对所有的回文中心进行扩展,直到无法扩展为止,此时的回文串长度即为此回文中心下的最长回文串长度。然后取所有的长度中的最大值,即可得到最终的答案。但是自己在写代码时出现了问题,自己只考虑到了回文串长度为奇数的情况,即以某个字符为中心然后同时向两边扩展。但其实还有一种情况,那就是以当前字符和其下一个相邻字符为中心再向两边扩展,此时得到的回文串长度就为偶数。所以代码一直写的有问题,最后的代码还是参考本题的官方题解。

相关题目:
LeetCode_回溯_动态规划_中等_131.分割回文串
LeetCode_双指针_简单_234.回文链表
LeetCode_贪心算法_简单_409.最长回文串
LeetCode_动态规划_中等_516.最长回文子序列
LeetCode_字符串_中等_647. 回文子串
LeetCode_双指针_简单_1332.删除回文子序列

3.代码实现(Java)

//思路1————暴力穷举法
class Solution {
    public String longestPalindrome(String s) {
        int maxLength = 0, length;
        String substr = "";
        for (int i = 0; i < s.length(); i++) {
            for (int j = i; j < s.length(); j++) {
                if (isPalindrome(s.substring(i, j + 1))) {
                    length = s.substring(i, j + 1).length();
                    if (length > maxLength) {
                        maxLength = length;
                        substr = s.substring(i, j + 1);
                    }
                }
            }
            //剩余子串的长度小于当前找到的回文子串的长度时,直接返回结果即可
            if (maxLength > s.length() - 1 - i) {
                return substr;
            }
        }
        return substr;
    }
    
    //判断字符串 str 是否为回文串
    public boolean isPalindrome(String str) {
        int length = str.length();
        int front = 0, rear = length - 1;
        while (front <= rear) {
            if (str.charAt(front) != str.charAt(rear)) {
                return false;
            }
            front++;
            rear--;
        }
        return true;
    }
}
//思路2————动态规划
class Solution {
    public String longestPalindrome(String s) {
        int length = s.length();
        //dp[i][j] 为 true 表示 s[i...j] 为回文串,否则不是
        boolean[][] dp = new boolean[length][length];
        //初始化,即长度为 1 的子串都是回文串
        for (int i = 0; i < length; i++) {
            dp[i][i] = true;
        }
        int start = 0;
        int maxLen = 1;
        //枚举子串长度 L
        for (int L = 2; L <= length; L++) {
            //枚举左边界 i,同时由 L 可以确定右边界
            for (int i = 0; i < length; i++) {
                //由 L 和 i 可以确定右边界 j,即有由 L = j - i + 1 得
                int j = L + i - 1;
                //此时当前子串为 s[i : j],如果右边界 j 越界,则直接退出当前循环
                if (j >= length) {
                    break;
                }
                //状态转移
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    if (i + 1 == j) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                //只要 dp[i][j] == true 成立,就表示子串 s[i...j] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && L > maxLen) {
                	start = i;
                    maxLen = L;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
}
//思路3————中心扩展法
class Solution {
    public String longestPalindrome(String s) {
        int length = s.length();
        int start = 0;
        int maxLen = 1;
        for (int i = 0; i < length - 1; i++) {
            int curLen = Math.max(expand(s, i, i), expand(s, i, i + 1));
            //更新回文串的起始位置和对应最大长度
            if (curLen > maxLen) {
            	start = i - (curLen - 1) / 2;
                maxLen = curLen;
            }
            //当剩下的字符串长度不足当前最长回文子串长度一半的时候,直接跳出循环
            if ((length - i + 1) * 2 < maxLen) {
                break;
            }
        }
        return s.substring(start, start + maxLen);
    }

    //以 s[left : right] 为中心开始扩展,返回得到的最大回文子串长度,此处的 left 可以等于 right
    public int expand(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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值