LeetCode Everyday:堅持價值投資,做時間的朋友!!!
題目:
給定一個字符串 s
,找到 s
中最長的迴文子串。你可以假設 s
的最大長度爲 1000。
示例:
- 示例 1
輸入: "babad" 輸出: "bab" 注意: "aba" 也是一個有效答案。
- 示例 2
輸入: "cbbd" 輸出: "bb"
代碼
方法一:暴力搜索法
- 思路核心:根據迴文子串的定義,枚舉所有長度大於等於2的子串,依次判斷它們是否是迴文
- 實現要點:
- 在具體實現時,可以只針對大於“當前得到的最長迴文子串長度”的子串進行“迴文驗證”;
- 在記錄最長迴文子串的時候,可以只記錄“當前子串的起始位置”和“子串長度”,不必做截取。這一步我們放在後面的方法中實現。
- 時間複雜度,空間複雜度
執行用時 :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]
- 時間複雜度,空間複雜度
執行用時 :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'
方法三:中心擴散法
- 思路核心:枚舉可能出現的迴文子串的“中心位置”,從“中心位置”嘗試儘可能擴散出去,得到一個迴文串。
- 實現要點:擴散的時候奇偶性是不同的,分開來寫。
- 時間複雜度,空間複雜度
執行用時 :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'
方法四:搜索改進法
- 思路核心:我認爲這是第一種方法的升級版本。
- 實現要點:相當於第二輪搜索只搜索可能是最長子串的兩種情況。
- 時間複雜度,空間複雜度
執行用時 :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 算法的技巧,充分挖掘了已經進行迴文判定的子串的特點,在遍歷的過程中,記錄了已經遍歷過的子串的信息,也是典型的以空間換時間思想的體現。
- 實現要點:這部分可以參考 題注
- 時間複雜度,空間複雜度
執行用時 :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