题目
给定一个字符串 _s_,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
题解
思路
回文串的特点是左右两端字符相同,要在s
前面添加字符串使得新字符串是回文串,那就把s
的倒置字符串放在前面就可以形成回文了。
s的倒置字符串添加在s前面会有什么问题?
这样添加还不是最短的回文。
比如s是abbacd,添加dcabba到开头,形成dcabbaabbacd,可以发现abba是多余的,不用添加,dcabbacd是更短的回文串,只要添加dc就行了。
应该添加什么样的字符串才能获得最短回文?
得找到abbacd的前缀和dcabba的后缀最长的重合的部分的长度,然后找到不重复的部分,添加到字符串前面。
标准化描述:
- 设
s'
是s
的倒置字符串。 - 求
s
的前缀和s'
的后缀中相同部分最长的长度x
。 - 取
s
的子串s[x, n - 1]
,倒置后添加到s
前面,形成最短回文。
怎么寻找s和s的倒置字符串最长前后缀?
联想到kmp做字符串匹配时,要先求一个前缀函数,前缀函数prefix[i]
表示s[0, i]
中最长的公共前后缀的长度,只要求出前缀函数就可以求解题目。
前缀函数怎么求?
一句话:让s[0, n - 1]
匹配s[1, n - 1]
。
算法步骤:
- 双指针
i
和j
遍历s
。 i
从1开始,j
从0开始。- s匹配自己,字符能匹配上,双指针都前进。
- 当前
s[i]
和s[j]
不匹配,要把j
往回退。j
退到前一个位置(j - 1
)的最长公共前后缀的前缀的下一个字符的位置。继续拿s[j]
跟s[i]
尝试匹配,如果能匹配的上,prefix[i]
就求出来了。- 如果
j
一直回退,最坏也就是回退到0,因为prefix[0] = 0
(一个字符不存在前后缀),没的退了,只能比较s[i]
和s[0]
,没匹配的话公共前后缀长度就是0。
前缀函数应该针对哪个字符串求?
我们要求s
的前缀和s
的倒置字符串的后缀最长的公共部分。
所以弄一个新字符串 s' + s
。
这样存在一个问题,如果所有字符都相同,此时最长公共前后缀就是整个字符串s' + s
本身,这不是期望的结果,所以连接s'
和s
时,中间隔一个用不到的字符,比如#
。
1 | class Solution { |
复杂度
设字符串s
长度为n。
- 时间复杂度O(n):求前缀函数最坏情况需要O(n + n) = O(n)时间;还要遍历整个
s' + s
,有2n的长度,需要O(n)时间。 - 空间复杂度O(n):前缀函数占用O(n)空间。
kmp时间复杂度推导可参考: