LeetCode题目(Python实现):最长回文子串

题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例1 :

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例2 :

输入: "cbbd"
输出: "bb"

想法一:以每个字符为中心向两边扫描

算法实现

def longestPalindrome(self, s: str) -> str:
    if not s:
        return ""
    res = s[0]
    n = len(s)
    for i in range(1, n):
        # 中轴情况
        j = 1
        while i - j > -1 and i + j < n and s[i - j] == s[i + j]:
            j += 1
        j -= 1
        if len(res) < j * 2 + 1:
            res = s[i - j:i + j + 1]

        # 对称情况
        j = 1
        while i - j > -1 and i + j - 1 < n and s[i - j] == s[i + j - 1]:
            j += 1
        j -= 1
        if len(res) < j * 2:
            res = s[i - j:i + j]
    return res

执行结果

执行结果 : 通过
执行用时 : 1176 ms, 在所有 Python3 提交中击败了68.60%的用户
内存消耗 : 13.6 MB, 在所有 Python3 提交中击败了33.03%的用户
在这里插入图片描述

复杂度分析

  • 时间复杂度:O(n^2)

  • 空间复杂度:O(1)

动态规划

算法实现

def longestPalindrome(self, s: str) -> str:
    size = len(s)
    if size < 2:
        return s

    dp = [[False for _ in range(size)] for _ in range(size)]

    max_len = 1
    start = 0

    for i in range(size):
        dp[i][i] = True

    for j in range(1, size):
        for i in range(0, j):
            if s[i] == s[j]:
                if j - i < 3:
                    dp[i][j] = True
                else:
                    dp[i][j] = dp[i + 1][j - 1]
            else:
                dp[i][j] = False

            if dp[i][j]:
                cur_len = j - i + 1
                if cur_len > max_len:
                    max_len = cur_len
                    start = i
    return s[start:start + max_len]

执行结果

在这里插入图片描述

复杂度分析

  • 时间复杂度:O(n^2)

  • 空间复杂度:O(n^2)

Manacher 算法

算法实现

def longestPalindrome(self, s: str) -> str:
    # 特判
    size = len(s)
    if size < 2:
        return s

    # 得到预处理字符串
    t = "#"
    for i in range(size):
        t += s[i]
        t += "#"
    # 新字符串的长度
    t_len = 2 * size + 1

    # 数组 p 记录了扫描过的回文子串的信息
    p = [0 for _ in range(t_len)]

    # 双指针,它们是一一对应的,须同时更新
    max_right = 0
    center = 0

    # 当前遍历的中心最大扩散步数,其值等于原始字符串的最长回文子串的长度
    max_len = 1
    # 原始字符串的最长回文子串的起始位置,与 max_len 必须同时更新
    start = 1

    for i in range(t_len):
        if i < max_right:
            mirror = 2 * center - i
            # 这一行代码是 Manacher 算法的关键所在,要结合图形来理解
            p[i] = min(max_right - i, p[mirror])

        # 下一次尝试扩散的左右起点,能扩散的步数直接加到 p[i] 中
        left = i - (1 + p[i])
        right = i + (1 + p[i])

        # left >= 0 and right < t_len 保证不越界
        # t[left] == t[right] 表示可以扩散 1 次
        while left >= 0 and right < t_len and t[left] == t[right]:
            p[i] += 1
            left -= 1
            right += 1

        # 根据 max_right 的定义,它是遍历过的 i 的 i + p[i] 的最大者
        # 如果 max_right 的值越大,进入上面 i < max_right 的判断的可能性就越大,这样就可以重复利用之前判断过的回文信息了
        if i + p[i] > max_right:
            # max_right 和 center 需要同时更新
            max_right = i + p[i]
            center = i

        if p[i] > max_len:
            # 记录最长回文子串的长度和相应它在原始字符串中的起点
            max_len = p[i]
            start = (i - max_len) // 2
    return s[start: start + max_len]

执行结果

在这里插入图片描述

复杂度分析

  • 时间复杂度:O(n)

  • 空间复杂度:O(n^2)

小结

先按自己的想法设计,也是之前做过的题,想了很久决定每个字符向两边扫描会好一点,然后做了出来。之后看了动态规划和 Manacher 算法,基本上理解了。

Manacher 算法说白了就是利用中心扩散的信息来省去一些计算,非常厉害,然后题解里面讲的很详细,虽然看了很久才明白,但是看完以后还是觉得大致理解了,很不错。

发布了112 篇原创文章 · 获赞 10 · 访问量 2893

猜你喜欢

转载自blog.csdn.net/qq_45556599/article/details/104829884
今日推荐