给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad” 输出: “bab” 注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd” 输出: “bb”
1 [自a] 暴力枚举 ——超时—— 87/103样例
自法1:用list[n] :时间n^2 空间n
自法2:在循环内 边循环边处理记录最大: 时间n^3 空间1
class Solution(object):
def isHW(self, s):
n = len(s)
if n%2==0:
end = n // 2 - 1
for i in range(0, end+1):
if s[i] != s[n-1-i]:
return 0
return 1
elif n%2==1:
end = n // 2
for i in range(0, end+1):
if s[i] != s[n - 1 - i]:
return 0
return 1
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
n = len(s)
strlist = []
for i in range(n): # 首字母 最后是s[n-2]
'''子串 可以是1个字符'''
str = ""
for j in range(i,n):
str += s[j]
strlist.append(str)
maxLen = 0
maxStr = ""
for str in strlist:
if self.isHW(str) and len(str)>maxLen:
maxLen = len(str)
maxStr = str
return maxStr
2 中心扩散法 ——微妙优化:空间O1——时复On^2
mh技巧:空间复杂度极好判断
只要出现list=[3,4,5,3]就是On,
全程没出现list=[3,4,5,3]就是O1
《 写中心扩散时 领悟的 方法论 》:
1 自己用 debug + 特例 [ 先草纸写值比较 要不必乱 ]
2 出错只关注出错的那部分 只要答题思路对,你就sb一样的只改那一小块即可
若改蒙蔽了 就新建自己最想要的新标记值调出来
‘’‘eg. 比较maxR时候老卡壳r就不用r了,直接用自己最想用的LocalLen简单有清晰!’’’
def longestPalindrome(s):
n = len(s)
'''(1)""空串'''
if n==0:
return ""
'''(4)ccc越改越错[因为根本综合不到一起去 数学隔离],直接拎出来处理得了!'''
ts = s[0]
v = 1
while v<n and ts==s[v]:
'''不长记性! 有while下标 一定先and出下标'''
v += 1
if v==n:
return s
maxR = -1
maxLen=0
for i in range(n):
'''新技巧:左右多少回文不定,用while!'''
'''开头左走一个或右走一个 可能左右都成立 可能奇数回文,但咱全都算了 就这么横!'''
"""(2)奇数回文"""
r1 = 0 # 回文半径
'''大技巧:涉及whlle配合下标的,一定 先and and后while 确保2哥下标不越界,因为while里面的判断也是普通代码执行会越界'''
while i-r1 >= 0 and i+r1 < n and s[i-r1] == s[i+r1]:
r1 += 1
Len1 = 1 + 2*(r1-1)
"""(3)偶数回文"""
'''左1'''
r2 = 0 # 回文半径
if i-1 >=0 and i<n and s[i-1] == s[i]: # 注意!! 所有下标都有限制,不要只限制核心下标
while i-1 -r2 >= 0 and i +r2 < n and s[i-1 -r2] == s[i +r2]:
r2 += 1
Len2 = r2*2
'''右1'''
r3 = 0
if i+1 <n and i>=0 and s[i] == s[i+1]:
while i+1 +r3 <n and i -r3 >= 0 and s[i -r3] == s[i+1 +r3]:
r3 += 1
Len3 = r3 * 2
'''【成功:信心大增!】2019年8月5日 07点19分:吃完早餐,自己用 debug + 特例[先草纸写值比较 要不必乱!,出错只关注出错的那部分 只要答题思路对,你就sb一样的只改那一小块即可,若思路混乱就新建自己最想要的新标记值,] 调出来,千万别看答案'''
'''比较maxR时候老卡壳r就不用r了,直接用自己最想用的LocalLen简单有清晰!'''
if max(Len1,Len2,Len3) > maxLen:
maxLen = max(Len1,Len2,Len3)
if maxLen==1:
r1 -= 1
'''maxLen=1 只可能是奇数对称'''
maxStr = s[i - r1: i + r1 + 1]
else:
'''mh自创:下面xxx -tmp的格式是mh的精髓,让mh极易理解【草纸上简单标出i i-1 立刻明白下方代码含义】'''
if Len2 == maxLen: # 同r 先选偶数的 因为对称中心比奇数 多一个字母
'''只关注特殊值的错误处理即可 别想太多 暴力!'''
r2 -=1 # 而且偶数tmp要-1 因为第一个有效r的1是对称中心
maxStr = s[i-1 -r2 : i +r2+1]
elif Len3 == maxLen:
r3 -= 1
maxStr = s[i - r3: i + 1 + r3 + 1]
elif Len1 == maxLen:
r1 -= 1
maxStr = s[i -r1 : i +r1+1]
return maxStr
3 中心扩散的leetcode答案非常精简巧妙,但我能写出上面的方法3,证明我是很nb的 debug方法让我接近无所不能
pass
4 动态规划法 —— 时间n^2 空间 n^2 ———— 虽101/103超时,但方法值得学习
(4.1) mh自己浅显易懂的原理解释: 从最小的单体开始一点点向大的判断回文,这样大的回文判断就能用上内部的小的dp[][]
自猜:只要处理好 if ”单b“ elif ”aa“ 这两种情况应该就可以了
pass
(4.2) 力扣答案 的 精妙方法:【答案的作者 也是碰巧想出的 硬碰的】
def longestPalindrome(s):
n = len(s)
if n==0 or n==1:
return s
dp = [[False for j in range(n)] for i in range(n)]
maxLen = 0
maxStr = ""
# '''新for的i从0起,随意重复用i!'''
'''用lr比ij好太多,直观清晰!'''
'''先分析好数组怎么构造的,因为j可能在i前面for!!'''
for r in range(1, n):
for l in range(r):
'''dp核心:==只判断 1次 边界,内部用之前的dp[][]'''
if s[l] == s[r]:
if l+1 == r-1: # "aba 不用管最中间的那个b了"
dp[l][r] = True
elif l+1 > r-1: # "aa"
dp[l][r] = True
elif l+1 < r-1: # 剩下的为一般普适情况
if dp[l+1][r-1] == True: # 画图后,发现之前的一定准备好了
dp[l][r] = True
elif s[1] != s[r]:
dp[l][r] = False
if dp[l][r]==True and r-l > maxLen:
maxLen = r-l
maxStr = s[l:r+1]
"""(1)ab maxStr下来是空,应该是a或b"""
if maxStr == "":
maxStr = s[0]
return maxStr
# for tmp in dp:
# print(tmp)
5 马拉车算法 ——最强—— O n
原理:收益串更新p[i]简化工作量 + while( i-p[i] i+p[i]) 的所有字符的从p[i]开始的最长回文判断
注意:之前我只看了i-p[i]一边,其实是为了确保s[对称回文]的左右边界而已
重要技巧:就是硬约束while条件 不行就debug!
def longestPalindrome(s):
n = len(s)
'''插#号,保证奇数个'''
new_s = ""
local = 0
j = 0 # 分隔#用
while local <= n-1: # local记录s中字符已经处理到的 下标
if j%2 == 0:
new_s += '#'
else:
new_s += s[local]
local += 1
j += 1
'''末尾差个#'''
new_s += '#'
new_len = len(new_s)
max = 0 # 注意:要保证max最右!,收益串的容量大小不看
mid = 0 # 最大右边界变时,mid才动
p = [] # p[i]核心数组
ans_maxNum = 0
for i in range(new_len):
"""收益串判断 是否可减少工作量"""
if max > i:
'''min的作用是保证左方回文串(很大可能经过i-p[i]后扩大到超过mx-i的最大半径。马拉车的收益串最大能用到mx-i的长度)'''
p.append(min(p[2*mid-i], max-i))
else:
p.append(1) # 开辟空间 要不p的长度是0 后面显示越界
"""收益串判断结束"""
"""所有字符都要执行:【0=<i-+p[i]<n】 一定要放后面,因为前面收益串会让你减少工作量,而且i-p[i]每个字符都要扩到最大"""
while i-p[i]>=0 and i+p[i]<new_len and new_s[i-p[i]]==new_s[i+p[i]]:
# 用特例 出的 >=0 eg.012 #a#
p[i] += 1
'''判断右边界max时候可右移'''
if i+p[i]>max: # 想第一个p[0],来写代码清晰了就
max = i + p[i]
mid = i
if p[i] > ans_maxNum:
ans_str = '' # 一定在for里初始化str ,要不就无限拼接了
ans_maxNum = p[i]
'''拼接字符串'''
tmp = p[i]-1
# 上面用j了 卧槽。。
ans_str = new_s[i-tmp:i+tmp+1]
tmpstr = ans_str.replace("#", "")
return tmpstr