一、题目
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
提示:
- 1 <= s.length <= 1000
- s 仅由数字和英文字母组成
二、题解
对于一个子串而言,如果它是回文串,并且长度大于2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串
“ababa”,如果我们已经知道“bab” 是回文串,那么“ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。
根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j)表示字符串s的第i到j个字母组成的串(下文表示成 s[i:j])是否为回文串:
P
(
i
,
j
)
=
{
t
r
u
e
,
如果子串
S
i
…
S
j
是回文串
f
a
l
s
e
,
其它情况
P(i,j)=\begin{cases}\mathrm{true},\quad&\text{如果子串 }S_i\ldots S_j\text{是回文串}\\\mathrm{false},\quad&\text{其它情况}\end{cases}
P(i,j)={true,false,如果子串 Si…Sj是回文串其它情况
这里的「其它情况」包含两种可能性:
∙
s
[
i
,
j
]
本身不是一个回文串
;
∙
i
>
j
,此时
s
[
i
,
j
]
本身不合法。
\begin{aligned}\\ \bullet&s[i,j]\text{ 本身不是一个回文串}; \\\bullet&i>j\text{,此时 }s[i,j]\text{ 本身不合法。} \end{aligned}
∙∙s[i,j] 本身不是一个回文串;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)\wedge(S_i==S_j)
P(i,j)=P(i+1,j−1)∧(Si==Sj)
也就是说,只有 s[i+1:j−1]是回文串,并且s的第i和j个字母相同时,s[i:j]才会是回文串。
以上的所有讨论是建立在子串长度大于2的前提之上的
考虑动态规划中的边界条件:
子串的长度为 1 或 2。
- 对于长度为1的子串,它显然是个回文串;
- 对于长度为2的子串,只要它的两个字母相同,它就是一个回文串。
因此我们就可以写出动态规划的边界条件:
{
P
(
i
,
i
)
=
true
P
(
i
,
i
+
1
)
=
(
S
i
=
=
S
i
+
1
)
\begin{cases}P(i,i)=\text{true}\\P(i,i+1)=(S_i==S_{i+1})\end{cases}
{P(i,i)=trueP(i,i+1)=(Si==Si+1)
根据这个思路,我们就可以完成动态规划了,最终的答案即为所有
P
(
i
,
j
)
=
true 中
j
−
i
+
1
P(i,j)=\text{true 中 }j-i+1
P(i,j)=true 中 j−i+1 (即子串长度)的最大值。
注意:
在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。
- 若为1个字符的字符串,直接返回,肯定是
- 创建dp[][]用来存储是否是回文子串
- 首先,动态规划最小单位,每一个字符构成一个回文子串
- 然后逐步向上扩展
- 起始,长度确定子串
- 如果子串两端不同,false
如果相同:- 是 1或2 个字符直接标记为true
- 不是2个字符,布尔值等于左右各缩进一个
- maxlen记录长度,如果子串长度大于maxlen,则更新
- 回文不一定是从头开始的随时更新起始位置
- 考虑越界,左边界越界、长度越界
三、代码
public String longestPalindrome(String s) {
int len = s.length();
// 只有一个字符的情况
if(len < 2){
return s;
}
// 动态规划
int maxLen = 1, begin = 0;
boolean[][] dp = new boolean[len][len]; // 用来表示是否为回文
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] c = s.toCharArray();
for(int l = 2; l <= len; l++){ //子串长度
for(int i = 0; i < len-2; i++){ // 子串起始位置 i
int j = i+l-1; // 子串结束位置 j
// 边界越界
if(j>len-1){
break;
}
if (c[i] != c[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) { // 不用再缩进
dp[i][j] = true;
} else { // 缩进继续判断
dp[i][j] = dp[i + 1][j - 1];
}
}
// 向外上合并停止的时刻
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin,begin+maxLen);
}
四、结果
五、说明
本文章仅用于记录个人做题记录
由于本人是个小菜鸡(实锤),题目解法并非最优,且解题过程中参考(抄袭)各大佬解题方法,望见谅。