📅  最后修改于: 2023-12-03 14:57:34.211000             🧑  作者: Mango
回文字符串是指正反读都一样的字符串。本文介绍如何计算给定的回文字符串的所有子字符串。
最简单的方法是暴力枚举所有可能的子字符串,然后逐一判断是否为回文。
def get_palindrome_substrings(s: str) -> List[str]:
res = []
for i in range(len(s)):
for j in range(i + 1, len(s) + 1):
if s[i:j] == s[i:j][::-1]:
res.append(s[i:j])
return res
该方法的时间复杂度为 $O(n^3)$,其中 $n$ 是字符串的长度。显然无法应对较长的字符串。
对于回文字符串,我们可以从中心往外扩散。具体地,对于一个回文字符串,可以选取其中任意一个位置为中心,然后向左右两边扩散,直到左右两端的字符不相同为止。如果字符串长度为奇数,则中心只有一个字符;如果字符串长度为偶数,则中心有两个字符。
def expand_around_center(s: str, left: int, right: int, res: List[str]) -> None:
while left >= 0 and right < len(s) and s[left] == s[right]:
res.append(s[left:right+1])
left -= 1
right += 1
def get_palindrome_substrings(s: str) -> List[str]:
res = []
for i in range(len(s)):
expand_around_center(s, i, i, res) # 中心为单个字符
expand_around_center(s, i, i+1, res) # 中心为两个字符
return res
该方法的时间复杂度为 $O(n^2)$,其中 $n$ 是字符串的长度。虽然比暴力枚举的方法更快,但对于较长的字符串仍然有瓶颈。
利用回文字符串的对称性,我们可以进一步优化方法二。具体地,我们可以先把字符串翻转一遍,然后找到原字符串和翻转后的字符串中的最长公共子串。因为这个最长公共子串一定是回文字符串,所以我们只需要再枚举一遍所有可能的回文中心即可。
def longest_common_substring(s: str, t: str) -> str:
m, n = len(s), len(t)
dp = [[0] * (n+1) for _ in range(m+1)]
max_len, end = 0, 0
for i in range(m):
for j in range(n):
if s[i] == t[j]:
dp[i+1][j+1] = dp[i][j] + 1
if dp[i+1][j+1] > max_len:
max_len = dp[i+1][j+1]
end = i
return s[end-max_len+1:end+1]
def get_palindrome_substrings(s: str) -> List[str]:
reversed_s = s[::-1]
lcs = longest_common_substring(s, reversed_s)
res = []
for i in range(len(lcs)):
expand_around_center(s, i, i, res) # 中心为单个字符
expand_around_center(s, i, i+1, res) # 中心为两个字符
return res
该方法的时间复杂度为 $O(n^2)$,相比方法二更为高效。