📌  相关文章
📜  计算给定字符串(其字谜是回文)的子字符串(1)

📅  最后修改于: 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)$,相比方法二更为高效。