LeetCode Everyday:坚持价值投资,做时间的朋友!!!
题目:
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例:
- 示例 1
输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。
- 示例 2
输入: "cbbd" 输出: "bb"
代码
方法一:暴力搜索法
- 思路核心:根据回文子串的定义,枚举所有长度大于等于2的子串,依次判断它们是否是回文
- 实现要点:
- 在具体实现时,可以只针对大于“当前得到的最长回文子串长度”的子串进行“回文验证”;
- 在记录最长回文子串的时候,可以只记录“当前子串的起始位置”和“子串长度”,不必做截取。这一步我们放在后面的方法中实现。
- 时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( 1 ) O(1) O(1)
执行用时 :7376 ms, 在所有 Python3 提交中击败了6.33%的用户
内存消耗 :13.7 MB, 在所有 Python3 提交中击败了9.26%的用户
class Solution1:
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
size = len(s)
if size < 2: return s
max_len = 1
res = s[0]
for i in range(size - 1):
for j in range(i+1, size):
if j-i+1 > max_len and self.__valid(s, i, j):
max_len = j-i+1
res = s[i:j+1]
return res
def __valid(self, s, left, right):
# 验证子串 s[left, right] 是否为回文串
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
"""
For Example: input: s = 'babad'
output: 'aba'
"""
s = 'babad'
solution = Solution1()
result = solution.longestPalindrome(s)
print('输出为:', result) # 'bab'
方法二:动态规划
- 思路核心:在头尾字符相等的情况下,里面子串的回文性质据定了整个子串的回文性质,这就是状态转移。
- 实现要点:状态转移方程:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
- 时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)
执行用时 :4436 ms, 在所有 Python3 提交中击败了36.66%的用户
内存消耗 :22.1 MB, 在所有 Python3 提交中击败了5.55%的用户
class Solution2:
def longestPalindrome(self, s):
"""
:type s: str
:rtype: 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 j in range(1, size):
for i in range(0, j):
dp[i][j] = (s[i] == s[j]) and (j - i < 3 or dp[i + 1][j - 1])
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]
"""
For Example: input: s = 'babad'
output: 'aba'
"""
s = 'babad'
solution = Solution2()
result = solution.longestPalindrome(s)
print('输出为:', result) # 'bab'
方法三:中心扩散法
- 思路核心:枚举可能出现的回文子串的“中心位置”,从“中心位置”尝试尽可能扩散出去,得到一个回文串。
- 实现要点:扩散的时候奇偶性是不同的,分开来写。
- 时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( 1 ) O(1) O(1)
执行用时 :1104 ms, 在所有 Python3 提交中击败了74.43%的用户
内存消耗 :13.7 MB, 在所有 Python3 提交中击败了9.26%的用户
class Solution3:
def expandAroundCenter(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
start, end = 0, 0
for i in range(len(s)):
left1, right1 = self.expandAroundCenter(s, i, i)
left2, right2 = self.expandAroundCenter(s, i, i + 1)
if right1 - left1 > end - start:
start, end = left1, right1
if right2 - left2 > end - start:
start, end = left2, right2
return s[start: end + 1]
"""
For Example: input: s = 'babad'
output: 'aba'
"""
s = 'babad'
solution = Solution3()
result = solution.longestPalindrome(s)
print('输出为:', result) # 'bab'
方法四:搜索改进法
- 思路核心:我认为这是第一种方法的升级版本。
- 实现要点:相当于第二轮搜索只搜索可能是最长子串的两种情况。
- 时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( 1 ) O(1) O(1)
执行用时 :76 ms, 在所有 Python3 提交中击败了98.46%的用户
内存消耗 :13.6 MB, 在所有 Python3 提交中击败了9.26%的用户
class Solution4:
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
res = ''
for i in range(len(s)):
start = max(0, i-len(res)-1)
temp = s[start : i+1]
if temp == temp[::-1]:
res = temp
else:
temp = temp[1:]
if temp == temp[::-1]:
res = temp
return res
"""
For Example: input: s = 'babad'
output: 'aba'
"""
s = 'babad'
solution = Solution3()
result = solution.longestPalindrome(s)
print('输出为:', result) # 'aba'
方法五:Manacher算法
- 思路核心:Manacher 算法本质上还是中心扩散法,只不过它使用了类似 KMP 算法的技巧,充分挖掘了已经进行回文判定的子串的特点,在遍历的过程中,记录了已经遍历过的子串的信息,也是典型的以空间换时间思想的体现。
- 实现要点:这部分可以参考 题注
- 时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( 1 ) O(1) O(1)
执行用时 :140 ms, 在所有 Python3 提交中击败了92.75%的用户
内存消耗 :13.5 MB, 在所有 Python3 提交中击败了9.26%的用户
class Solution5:
def expand(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return (right - left - 2) // 2
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
end, start = -1, 0
s = '#' + '#'.join(list(s)) + '#'
arm_len = []
right = -1
j = -1
for i in range(len(s)):
if right >= i:
i_sym = 2 * j - i
min_arm_len = min(arm_len[i_sym], right - i)
cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len)
else:
cur_arm_len = self.expand(s, i, i)
arm_len.append(cur_arm_len)
if i + cur_arm_len > right:
j = i
right = i + cur_arm_len
if 2 * cur_arm_len + 1 > end - start:
start = i - cur_arm_len
end = i + cur_arm_len
return s[start+1:end+1:2]
"""
For Example: input: s = 'babad'
output: 'aba'
"""
s = 'babad'
solution = Solution3()
result = solution.longestPalindrome(s)
print('输出为:', result) # 'bab'
测试
- 提交后的执行用时,只能作为参考,它和很多因素有关,包括数据的维度,数据的特殊性,机器的性能等等
- 而且同样的时间复杂度,由于具体情况的不同,每个复杂度乘以的比例,其他未算入的低阶项等等都会带来不一样的时间损耗
- 甚至同一个算法的不同实现,实际用时都是不一样的
- 实际工程也是需要权衡各种利弊,具体问题具体分析
以s = 'aaaaa'*1000
为例,测试结果如下:
solution | solution1 | solution2 | solution3 | solution4 | solution5 |
---|---|---|---|---|---|
time | 1.61s | 5.28s | 2.26s | 16.7ms | 11.5ms |
参考
- https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
- https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
- https://www.bilibili.com/video/BV1d7411U7dw?from=search&seid=112685129946594939