自学内容网 自学内容网

【LeetCode】修炼之路-0005-Longest Palindromic Substring【python】

题目

Given a string s, return the longest palindromic substring in s.

Example 1:

Input: s = “babad”
Output: “bab”
Explanation: “aba” is also a valid answer.

Example 2:

Input: s = “cbbd”
Output: “bb”

前言

首先,题目我们就看不懂 ,上网冲浪一下发现, longest palindromic substring是最长回文字串的意思,就是从左右对称的一个数组。

思路1:暴力

我们把所有可能都罗列出来,然后判断每个是不是回文字符串。

判断是不是回文字符串

回文字符串怎么判断呢,我们可以比头尾,比如
对于偶数的字符串s=‘’abba’,长度len(s)是4,
我们可以从start=0,end=3开始

case1 偶数长度的字符串

对于字符串 “abba”:

√ 步骤 1 (start = 0, end = 3):

abba
下标0123
指针startend

√ 步骤 2 (start = 1, end = 2):

abba
下标0123
指针startend

X 步骤 3 (start = 2, end = 1):

abba
下标0123
指针endstart

我们通过步骤1和步骤2去观察我们需要的循环条件,然后看我们的循环条件怎么写

1.我们需要的整个过程start=0,1 end是3,2,start始终处在小于end的状态下,所以我们的循环条件可以写start<end:
参考代码:

def is_palindrome(s):
    start = 0
    end = len(s) - 1
    
    while start < end:
        if s[start] != s[end]:
            return False # 不符合就终止
        start += 1 # start往后递进
        end -= 1 # end往前递进
    
    return True

2.因为是对称的,所以我们的start一定是走到一半就停止,即:start< len(s)//2 也可以做为我们的循环条件

def is_palindrome(s):
    start = 0
    end = len(s) - 1
    
    while start < len(s) // 2:
        if s[start] != s[end]:
            return False # 不符合就终止
        start += 1 # start往后递进
        end -= 1 # end往前递进
    
    return True

我们还可以怎么写呢,while循环是从状态的思路去考虑循环的,实际上,第二种思路,我们知道我们要循环多少次,通过次数去写循环,我们还可以用 for i in rang() 这种写法

def is_palindrome(s):
    length = len(s)
    end = length - 1
    
    for start in range(length // 2):
        if s[start] != s[end]:
            return False # 不符合就终止
        end -= 1 # end往前递进
    
    return True

精简版本:

def is_palindrome(s):
    length = len(s)
    for i in range(length // 2):
        if s[i] != s[length - 1 - i]:
            return False
    return True

然后我们得到偶数情况下的代码了,我们考虑一下奇数情况下会发生什么。

case2 对于奇数长度的字符串

对于字符串 “abcba”:

步骤 1 (start = 0, end = 4):

abcba
下标01234
指针startend

步骤 2 (start = 1, end = 3):

abcba
下标01234
指针startend

步骤 3 (start = 2, end = 2):

abcba
下标01234
指针start/end

看上去我们似乎需要修改我们的代码,因为这里的循环条件似乎变成了start ≤ end,有些新东方的学员可能就非常有实干精神去改代码了,然后说,看我做了奇偶条件判断,我真是逻辑严密得滴水不漏啊!

为了帮助大家建立更好的思维路径,我们进一步思考一下这个问题,对于判断回文字符串,我们是不是还可以用递归的思想来看这个问题。我们可以降低这个问题的规模,然后一步一步解决吗?

让我们从空字符串,单个字符,两个字符,三个字符依次来思考:

我们 让 P(n) 表示判断长度为 n 的字符串是否为回文字符串的问题。(特别地让 *P(2) 表示判断字符串首尾字符是否一致。)
*P(2) = (s[0] == s[n-1]) (判断首尾字符是否相同)

基本情况:
P(0) = true (空字符串是回文)
P(1) = true (单个字符总是回文)

递归关系:
对于 n ≥ 2:
P(2) = *P(2) + P(0)=*P(2)
P(3) = *P(2) + P(1)=*P(2)
P(4) = *P(2) + P(2)=*P(2)+*P(2)
P(5) = *P(2) + P(3)=*P(2)+*P(2)

我们可以看到对于P(n)问题的n的奇偶问题,实际上p(奇)是等价于p(奇-1)的。也就是说我们不用对奇数做额外的处理。
在这里插入图片描述

*扩展了解:我们可以得到更一般的一般形式:
对于 n ≥ 2:
P(n) = P(2) + P(n-2)

def is_palindrome(s):
    def P(n, start):
        # 基本情况
        if n <= 1:
            return True
        
        # *P(2): 判断首尾字符是否相同
        p2_star = (s[start] == s[start + n - 1])
        
        # 递归调用
        return p2_star and P(n - 2, start + 1)
    
    return P(len(s), 0)

主函数代码

我们发现,真要暴力遍历,是个很复杂的过程,因为我们要判断1+2+…+n次,例如对于“abcd”这个字符串,我们要找出所有的字串并遍历的话,

长度为1的子串:
对于一个长度为n的字符串,长度为1的子串有n个。
例如,“abcd” 的长度为1的子串有:[“a”, “b”, “c”, “d”],共4个。

长度为2的子串:
长度为2的子串有n-1个。
例如,“abcd” 的长度为2的子串有:[“ab”, “bc”, “cd”],共3个。

长度为3的子串:
长度为3的子串有n-2个。
例如,“abcd” 的长度为3的子串有:[“abc”, “bcd”],共2个。

依此类推,直到:
长度为n的子串(整个字符串本身)只有1个。

总结这个模式:
长度为1的子串:n个
长度为2的子串:n-1个
长度为3的子串:n-2个

长度为n-1的子串:2个
长度为n的子串:1个

数学表达:
总子串数 = n + (n-1) + (n-2) + … + 2 + 1
即等差数列的求和,结果为:n * (n + 1) / 2

这样的暴力写法本身都变得太复杂,我们是不是可以用之前降低问题规模的思路,重新思考一下呢。
我们整理一下思路,因为目的是要找出给定字符串中最长的回文子串。

所以我们算法的主干核心思想如下:
从最长可能的子串开始检查,逐步缩小长度,直到找到一个回文子串。

代码逻辑分解:
a. 从整个字符串长度n开始遍历到0。【最外层循环】

for length in range(n, 0, -1):
# todo

b. 检查所有该长度的子串是否为回文。 【内层循环】

for start in range(n - length + 1):
if is_palindrome(s[start:start+length]):
# todo

c. 如果找到回文,立即返回。【结束】

for start in range(n - length + 1):
if is_palindrome(s[start:start+length]):
return s[start:start+length]

d. 如果没有找到,最后返回空值【边界处理】

return ''

实现策略:
外层循环控制子串长度,从最长到最短。
内层循环遍历所有可能的起始位置。
使用之前的辅助函数is_palindrome检查子串是否为回文。

那么最后我们可以得到我们的粗糙版本算法

思路1 粗糙版本的最终代码

class Solution:
    def longestPalindrome(self, s: str) -> str:
        def is_palindrome(start, end):
            while start < end:
                if s[start] != s[end]:
                    return False
                start += 1
                end -= 1
            return True

        n = len(s)
        
        # 从最长的可能长度开始
        for length in range(n, 0, -1):
            # 检查所有这个长度的子串
            for start in range(n - length + 1):
                if is_palindrome(start, start + length - 1):
                    return s[start:start+length]
        
        # 如果没有找到回文(这种情况实际上不会发生,因为单个字符总是回文)
        return ""

提交结果

测试通过啦!恭喜我们,成功解决了一个问题,虽然还不是最优的,但是作为初学者,我们已经很厉害了!
在这里插入图片描述
(我们的解法不是最优的,如果出现下面的结果,只是运气好而已,不要骄傲哦!)
在这里插入图片描述

思路2 动态规划

有人想听吗

思路3 Manacher算法

点赞达到100就更新(疯狂画饼)


原文地址:https://blog.csdn.net/crazyjinks/article/details/142832578

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!