主要記錄解題過程,反思如何構思代碼。
原題:https://leetcode-cn.com/problems/longest-palindromic-substring
題目:
給定一個字符串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。
示例 1:
輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
示例 2:
輸入: "cbbd"
輸出: "bb"
解題過程
看到這題一開始是完全懵逼的,看着兩個例子想了一個錯的解法:用兩個指針指向字符串的首尾,當兩個指針所指的內容相同時,記錄下兩個索引值,不同時索引值歸零,直到兩個指針相遇。
這個解法很明顯是錯的,因為它假定了最長回文子串的中心一定是字符串中心
這個時候我看到了題目的三條提示:
How can we reuse a previously computed palindrome to compute a larger palindrome?
If “aba” is a palindrome, is “xabax” and palindrome? Similarly is “xabay” a palindrome?
Complexity based hint:
If we use brute-force and check whether for every start and end position a substring is a palindrome we have O(n^2) start - end pairs and O(n) palindromic checks. Can we reduce the time for palindromic checks to O(1) by reusing some previous computation.
前兩條提示非常有用,我想到回文的特征是:呈中心對稱。即,兩個指針以同樣的步幅從回文的中心出發,所指內容應該會一直保持一致。應該還是使用兩個指針,但是是從內到外走,而不是從外到內
總體算法思路為:遍歷整個字符串,將每個位置當作回文中心去找以這個字符為中心能形成的最長回文,然后選出整個字符串最長的回文
這里有一個很干擾我思路的問題出現了:就是'cbbd'這種情況。我的算法是無法輸出'bb'的,在這個地方卡了很久。后來我決定把這種情況特殊處理,如果第i個字符串與第i+1個字符串相同,則將尾指針往后移。
我覺得有一個經驗就是,在時間緊迫的時候,先把自己能想出來的完整算法寫出來,再想辦法去處理特殊情況,否則在那空想是很浪費時間的。
第一版:
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
longest_palindrome = {'length': 0, 'str': ''}
for i in range(n):
index1 = index2 = i
if i+1 < n and s[i+1] == s[i]:
index1 = i
index2 = i+1
while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
index1 += -1
index2 += 1
length = index2 - index1 + 1
if length > longest_palindrome['length']:
longest_palindrome['length'] = length
longest_palindrome['str'] = s[index1:index2+1]
return longest_palindrome['str']
結果:不通過
不通過的用例:
輸入:
"ccc"
輸出:
"cc"
預期:
"ccc"
在第二個c的位置,由於第三個c與第二個c相等,則index1=1,index2=2,將回文的中心變成了這兩個字符,所以出錯。
解決的思路是:找全所有所有相同的字符組成一個回文中心,即在處理第一個c的時候,就一直往后找,直到回文中心變為"ccc"
第二版:
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
longest_palindrome = {'length': 0, 'str': ''}
for i in range(n):
index1 = index2 = i
while index2+1 < n and s[index1] == s[index2+1]:
index2 += 1
while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
index1 += -1
index2 += 1
length = index2 - index1 + 1
if length > longest_palindrome['length']:
longest_palindrome['length'] = length
longest_palindrome['str'] = s[index1:index2+1]
return longest_palindrome['str']
可以再優化一下,不要每次找到更長的回文就切出來,只需要記下索引就好了
第三版:
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
l_len = 0
l_index1 = 0
l_index2 = 0
for i in range(n):
index1 = index2 = i
while index2+1 < n and s[index1] == s[index2+1]:
index2 += 1
while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
index1 += -1
index2 += 1
length = index2 - index1 + 1
if length > l_len:
l_len = length
l_index1 = index1
l_index2 = index2
return s[l_index1: l_index2+1]
二刷
就因為加了“下一次mid的選擇越過相同的字符”這一個操作,執行用時從800ms降到90ms
應該是測試用例中有很多連在一起的相同字符
class Solution:
def longestPalindrome(self, s: str) -> str:
# 遍歷s,將每個字符當作回文中心擴散
n = len(s)
if n == 0:
return ""
# 記錄最長的回文子串,初始為第一個字符
res = s[0]
# 回文中心
mid = 0
while mid < n:
# left和right指向回文子串的前一個字符和后一個字符
left, right = mid - 1, mid + 1
# 找全所有相同的字符組成回文中心
while right < n and s[right] == s[mid]:
right += 1
# 下一次mid的選擇應該越過這些相同的字符
mid += 1
while left >= 0 and right < n and s[left] == s[right]:
left -= 1
right += 1
# 當前回文串的長度
if right - left - 1 > len(res):
res = s[left+1:right]
mid += 1
return res
時間復雜度:O(n2)。最多有n個回文中心,每個回文中心最多向外拓展n次
空間復雜度:O(1)