又开始刷题了,去年被这道题虐过,今天终于写了一个还能看的版本。
这个版本肯定不是最优,这篇博客主要记录一下解题的过程,反思如何构思代码。
最长回文子串
来源:力扣(LeetCode)
链接: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']
通过是通过了。就是执行时间980ms,位于前32%不太满意。可以再优化一下,不要每次找到更长的回文就切出来,只需要记下索引就好了
第三版:
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]
执行时间优化至852ms,位于前26%。就这样吧,三个小时已经过去了,写算法题并不是写业务逻辑,性能比易读性更重要