本文我们来寻找隐藏在字符串中的小妖精——最长回文子串。
引言
大家好!今天我们来聊一聊一个有趣的问题:如何在一个字符串中找到最长的回文子串。这道题是LeetCode上的第5题,乍一看,好像是在跟我们玩捉迷藏。回文子串是什么?它其实就是那些可以正着读倒着读都一样的字符串,比如“madam”或者“racecar”。接下来,我们要开启一段充满魔法和智慧的旅程,揭开这个隐藏在字符串中的小妖精——最长回文子串。
解题思路
在找到最长回文子串之前,我们需要一点策略。我们有几种方法来解决这个问题,像是暴力破解法、中心扩展法和动态规划法。今天我们重点讲解的是中心扩展法,它就像是我们站在一个舞台中央,向四周发出魔法光波,寻找最长的回文。
中心扩展法
中心扩展法的基本思路是这样的:每个字符(甚至每个字符之间的空隙)都可能是一个回文中心。我们从每个中心开始,向两边扩展,看看能找到多长的回文子串。最后,我们选取最长的那个。
详细步骤
-
初始化变量
start
和end
用来记录最长回文子串的起始和结束位置。- 遍历字符串,每个字符和字符之间的位置都作为回文中心。
-
扩展回文
- 对每个中心,分别尝试扩展奇数长度和偶数长度的回文。
- 更新最长回文的起始和结束位置。
-
返回结果
- 根据记录的起始和结束位置,截取字符串并返回。
实现代码
public class LongestPalindrome {
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);
}
private 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;
}
}
解题过程可视化
使用Mermaid语法来展示这个过程,如下图所示:
代码详解
- 初始化变量
int start = 0, end = 0;
这里,我们初始化了两个变量start
和end
,用来记录最长回文子串的起始和结束位置。
- 遍历字符串,选择中心
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;
}
}
我们遍历整个字符串,假设每个字符和每个字符之间的空隙都是一个潜在的回文中心。
- 扩展回文
private 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;
}
在expandAroundCenter
方法中,我们从中心开始,向两边扩展,直到无法扩展为止。返回扩展后的回文长度。
- 更新最长回文的起始和结束位置
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
如果找到的回文长度比当前记录的最长回文更长,就更新起始和结束位置。
- 截取最长回文子串
return s.substring(start, end + 1);
最后,根据记录的起始和结束位置,截取并返回最长的回文子串。
例子讲解
例子1:字符串 “babad”
让我们通过字符串 “babad” 来解释这个过程:
-
初始状态
start = 0
,end = 0
- 当前最长回文子串是第一个字符 “b”
-
遍历字符串并扩展回文
-
中心在 ‘b’ (i = 0)
- 奇数长度扩展:“b” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 0
(最长回文 “b”)
-
中心在 ‘a’ (i = 1)
- 奇数长度扩展:“bab” (len1 = 3)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 2
(最长回文 “bab”)
-
中心在 ‘b’ (i = 2)
- 奇数长度扩展:“aba” (len1 = 3)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 1
,end = 3
(最长回文 “aba”)
-
中心在 ‘a’ (i = 3)
- 奇数长度扩展:“a” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 1
,end = 3
(最长回文 “aba”)
-
中心在 ‘d’ (i = 4)
- 奇数长度扩展:“d” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 1
,end = 3
(最长回文 “aba”)
-
-
返回结果
- 截取
s.substring(start, end + 1) = "aba"
- 截取
例子2:字符串 “cbbd”
让我们通过字符串 “cbbd” 来解释这个过程:
-
初始状态
start = 0
,end = 0
- 当前最长回文子串是第一个字符 “c”
-
遍历字符串并扩展回文
-
中心在 ‘c’ (i = 0)
- 奇数长度扩展:“c” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 0
(最长回文 “c”)
-
中心在 ‘b’ (i = 1)
- 奇数长度扩展:“b” (len1 = 1)
- 偶数长度扩展:“bb” (len2 = 2)
- 更新:
start = 1
,end = 2
(最长回文 “bb”)
-
中心在 ‘b’ (i = 2)
- 奇数长度扩展:“b” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 1
,end = 2
(最长回文 “bb”)
-
中心在 ‘d’ (i = 3)
- 奇数长度扩展:“d” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 1
,end = 2
(最长回文 “bb”)
-
-
返回结果
- 截取
s.substring(start, end + 1) = "bb"
- 截取
例子3:字符串 “a”
让我们通过字符串 “a” 来解释这个过程:
-
初始状态
start = 0
,end = 0
- 当前最长回文子串是第一个字符 “a”
-
遍历字符串并扩展回文
- 中心在 ‘a’ (i = 0)
- 奇数长度扩展:“a” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 0
(最长回文 “a”)
- 中心在 ‘a’ (i = 0)
-
返回结果
- 截取
s.substring(start, end + 1) = "a"
- 截取
例子4:字符串 “ac”
让我们通过字符串 “ac” 来解释这个过程:
-
初始状态
start = 0
,end = 0
- 当前最长回文子串是第一个字符 “a”
-
遍历字符串并扩展回文
-
中心在 ‘a’ (i = 0)
- 奇数长度扩展:“a” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 0
(最长回文 “a”)
-
中心在 ‘c’ (i = 1)
- 奇数长度扩展:“c” (len1 = 1)
- 偶数长度扩展:“” (len2 = 0)
- 更新:
start = 0
,end = 0
(最长回文 “a”)
-
-
返回结果
- 截取
s.substring(start, end + 1) = "a"
- 截取
总结
通过这几个例子,我们展示了如何使用中心扩展法找到字符串中的最长回文子串。这个方法的核心在于从每个可能的中心开始扩展,寻找最长的回文子串。尽管时间复杂度是O(n^2),但对于大多数实际情况已经足够高效。
通过这些例子,我们可以看到,无论是字符串中有多个回文子串还是只有一个字符的字符串,中心扩展法都能够有效地找到最长的回文子串。希望这个幽默风趣的解题过程能帮助你更好地理解中心扩展法。
如果本文对您有所帮助的话,请收藏文章、关注作者、订阅专栏,感激不尽。