无用

103 阅读4分钟

最长回文子串问题是指,在给定的字符串中,找到最长的回文子串。回文串是指正着读和反着读都一样的字符串。

解法一:动态规划(Dynamic Programming)

这种方法比较直观,时间复杂度为 O(n²),空间复杂度为 O(n²)。

思路:

  1. 定义一个二维的布尔数组 dp[i][j],表示字符串从索引 ij 是否是回文串。
  2. 如果 s[i] == s[j],并且 dp[i+1][j-1] 是回文串,那么 dp[i][j] 也是回文串。
  3. 动态规划的转移方程为:dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]
  4. 初始条件:
    • 单个字符总是回文串,所以 dp[i][i] = true
    • 两个连续相同的字符也是回文串,所以 dp[i][i+1] = (s[i] == s[i+1])
  5. 在填表的过程中,记录最长的回文子串的开始和长度。

代码实现:

def longest_palindrome(s: str) -> str:
    if not s:
        return ""
    
    n = len(s)
    # dp[i][j] 表示 s[i:j+1] 是否为回文串
    dp = [[False] * n for _ in range(n)]
    start = 0  # 记录最长回文串的起始位置
    max_len = 1  # 记录最长回文串的长度
    
    # 所有长度为 1 的子串都是回文串
    for i in range(n):
        dp[i][i] = True
        
    # 处理长度为 2 的子串
    for i in range(n - 1):
        if s[i] == s[i + 1]:
            dp[i][i + 1] = True
            start = i
            max_len = 2
    
    # 处理长度大于 2 的子串
    for length in range(3, n + 1):  # length 是子串的长度
        for i in range(n - length + 1):
            j = i + length - 1  # 子串的结束位置
            if s[i] == s[j] and dp[i + 1][j - 1]:
                dp[i][j] = True
                start = i
                max_len = length
    
    return s[start:start + max_len]

时间复杂度:

  • 动态规划表的填充需要两个嵌套的循环,因此时间复杂度是 O(n²)。

空间复杂度:

  • 需要一个二维的 DP 数组,空间复杂度为 O(n²)。

解法二:中心扩展法(Expand Around Center)

这种方法的时间复杂度为 O(n²),空间复杂度为 O(1)。

思路:

  1. 回文串的中心可以是一个字符,也可以是两个字符。
  2. 对于每一个字符,考虑它作为回文串的中心,向两边扩展,找到最长的回文串。
  3. 重复这个过程,找到最长的回文子串。

代码实现:

def longest_palindrome(s: str) -> str:
    if not s:
        return ""
    
    def expand_around_center(left: int, right: int) -> str:
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        # 返回扩展后的最长回文子串
        return s[left + 1:right]
    
    longest = ""
    for i in range(len(s)):
        # 以 s[i] 为中心扩展
        odd_palindrome = expand_around_center(i, i) 
        # 以 s[i] 和 s[i+1] 为中心扩展
        even_palindrome = expand_around_center(i, i + 1)
        
        # 更新最长回文子串
        longest = max(longest, odd_palindrome, even_palindrome, key=len)
    
    return longest

时间复杂度:

  • 中心扩展的时间复杂度是 O(n²),每一个字符都尝试作为中心向两边扩展。

空间复杂度:

  • 只需要常数级别的额外空间,因此空间复杂度为 O(1)。

解法三:Manacher's Algorithm(马拉车算法)

这是一个针对回文子串的线性时间算法,时间复杂度为 O(n)。

思路:

  1. 通过预处理将字符串转换为一个新的字符串,插入特殊字符(如 #)以避免处理奇偶长度的回文子串问题。
  2. 使用一个数组 p 记录以每个字符为中心的回文子串的半径长度。
  3. 维护两个变量:
    • C:当前回文子串的中心。
    • R:当前回文子串的右边界。
  4. 当遍历到一个新的位置时,利用之前已经计算的回文信息进行状态转移,减少重复计算。

代码实现:

def longest_palindrome(s: str) -> str:
    # 特殊情况处理
    if not s:
        return ""
    
    # 预处理字符串
    T = '#'.join(f'^{s}$')
    n = len(T)
    p = [0] * n
    C = R = 0
    max_len = 0
    center_index = 0
    
    for i in range(1, n - 1):
        mirror = 2 * C - i  # i 关于 C 的对称位置
        
        if R > i:
            p[i] = min(R - i, p[mirror])
        
        # 尝试扩展以 i 为中心的回文串
        while T[i + p[i] + 1] == T[i - p[i] - 1]:
            p[i] += 1
        
        # 如果扩展后的回文串超过了 R,则更新 C 和 R
        if i + p[i] > R:
            C = i
            R = i + p[i]
        
        # 记录最长回文串的长度和中心位置
        if p[i] > max_len:
            max_len = p[i]
            center_index = i
    
    # 从处理后的字符串中找到最长回文串的起始位置
    start = (center_index - max_len) // 2
    return s[start:start + max_len]

时间复杂度:

  • 这个算法的时间复杂度是 O(n),因为每个字符最多只被访问两次。

空间复杂度:

  • 需要 O(n) 的空间来存储预处理后的字符串和半径数组 p

总结:

  • 动态规划:适用于理解简单,但时间和空间复杂度较高 (O(n²))。
  • 中心扩展法:实现简单,适用于大多数情况,时间复杂度 O(n²),空间复杂度 O(1)。
  • Manacher's Algorithm:时间复杂度为 O(n),是解决最长回文子串的最优算法,但实现较复杂。