214. 最短回文串 (JAVA)
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
示例 1:
输入:s = "aacecaaa"
输出:"aaacecaaa"
示例 2:
输入:s = "abcd"
输出:"dcbabcd"
提示:
0 <= s.length <= 5 * 10^4
s 仅由小写英文字母组成
解题思路:
首先想到先将待处理的字符串s翻转得到t,然后比较原字符串s和翻转字符串t,从第一个字符开始逐一比较,如果相等,说明s本身就是回文串,直接返回即可;如果不相等,s去掉最后一位,t去掉第一位,继续比较,以此类推直至有相等,或者循环结束,这样就能将两个字符串在正确的位置拼接起来了,但这种写法却会超时 TLE
class Solution { public: string shortestPalindrome(string s) { string t = s; reverse(t.begin(), t.end()); int n = s.size(), i = 0; for (i = n; i >= 0; --i) { if (s.substr(0, i) == t.substr(n - i)) { break; } } return t.substr(0, n - i) + s; } };
改进:这里主要的小号是substring过多,改进方法是使用双指针来找出字符串s的最长回文前缀的大概范围。方法是指针i和j分别指向s串的开头和末尾,若 s[i] 和 s[j] 相等,则i自增1,j自减1,否则只有j自减1。需要注意的是,这样遍历一遍后,得到的范围 [0, i) 中的子串并不一定就是最大回文前缀,也可能还需要添加字符,举个例子来说,对于 "adcba",在 for 循环执行之后,i=2,可以发现前面的 "ad" 并不是最长回文前缀,其本身甚至不是回文串,需要再次调用递归函数来填充使其变为回文串,但可以确定的是可以添加最少的字符数让其变为回文串。而且可以确定的是后面剩余的部分肯定不属于回文前缀,此时提取出剩下的字符,翻转一下加到最前面,而对范围 [0, i) 内的子串再次递归调用本函数,这样,在子函数最终会组成最短的回文串,从而使得整个的回文串就是最短的。
class Solution { public String shortestPalindrome(String s) { //先找到完全没有重复出现的尾部字符串 int i = 0, j = s.length() - 1; for(; j >= 0; j--){ if(s.charAt(i) == s.charAt(j)) i++; } if(i == s.length()) return s; StringBuilder sb = new StringBuilder(s.substring(i)); String r = sb.reverse().toString(); //递归剩余的字符串 return r + shortestPalindrome(s.substring(0,i)) + s.substring(i); } }
其实这道题的最快的解法是使用 KMP 算法
class Solution { public: string shortestPalindrome(string s) { string r = s; reverse(r.begin(), r.end()); string t = s + "#" + r; vector<int> next(t.size(), 0); for (int i = 1; i < t.size(); ++i) { int j = next[i - 1]; while (j > 0 && t[i] != t[j]) j = next[j - 1]; next[i] = (j += t[i] == t[j]); } return r.substr(0, s.size() - next.back()) + s; } };