https://leetcode.com/problems/shortest-palindrome/description/
Given a string s, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.
Example 1:
Input:"aacecaaa"
Output:"aaacecaaa"
Example 2:
Input:"abcd"
Output:"dcbabcd"
这一题最基本的思路是这样的,因为是往前加字符,那么加字符加的最少的情况是基于已经存在的某个最长palindrome子串,这个子串起点是input字符串的起点,然后将多余部分对称补足就好。
譬如给定的例子里是aacecaaa,那么可以看到,前面七个字符aacecaa就是一个有效且最长的子palindrome串,然后把后面剩下的a往前补足就好。所以最后的结果是aaacecaaa。再举一个简单的例子,abbabbd,因为在这个子字符串里,abba是基于input起点的最长子字符串。所以把bbd往前面补齐,也就是前面加一个dbb就好,所以最后结果是dbbabbabbd
基于上述算法,给出代码如下:
public String shortestPalindrome(String s) {
int maxLen = 0;
for (int i = s.length(); i >= 1 && maxLen == 0; i--) {
if (isPalindrome(s, i)) maxLen = i;
}
StringBuilder sb = new StringBuilder();
for (int i = s.length() - 1; i >= maxLen; i--) {
sb.append(s.charAt(i));
}
sb.append(s);
return sb.toString();
}
private boolean isPalindrome(String s, int length) {
int end = length - 1;
int start = 0;
while (start < end) {
if (s.charAt(start) != s.charAt(end)) {
return false;
}
start++;
end--;
}
return true;
}
检查一个字符串的方法比较直观,代码里看就好,之前的博客文也有了。算法复杂度是o(n^2)。这种做法是可以过leetcode的,只是你的运行时间大概是在bottom 10%的那一批的。所以,我们需要,优化一下。。
这个优化其实不是在基础的算法思想上有很大的改变,只是在string match上做出变化。引用了kmp的概念。kmp最基础的用例其实是find substring。它将find substring的过程从O(m * n) time O(1) space的,变成了O(m + n) time O(m) space的。其中m是substring的长度,n是主string的长度。
kmp本质是依靠一个辅助的跳转数组将原本暴力匹配中每次失配带来的损失降到最低。 具体嘛,看下面这篇文章
https://blog.csdn.net/v_july_v/article/details/7041827
kmp最重要的是目标字符串所形成的pattern跳转数组,下面给出代码
private int[] buildNexts(char[] sArr) {
int[] nextPattern = new int[sArr.length];
nextPattern[0] = -1;
nextPattern[1] = 0;
for (int i = 2; i < sArr.length; i++) {
int p = nextPattern[i - 1];
while (p >= 0 && sArr[i - 1] != sArr[p]) {
p = nextPattern[p];
}
nextPattern[i] = p + 1;
}
return nextPattern;
}
上面的代码和上面给出的文章链接里的有所不同,但看起来更简洁,所以我就"借鉴"了一下
https://leetcode.com/problems/shortest-palindrome/discuss/125992/Plain-KMP-beat-97.4-no-need-junct-2-strings-together
举个简单的例子。如果目标字符串是"abcabdbbab"。那么形成的辅助跳转数组就会是[-1, 0, 0, 0, 1, 0, 0, 0, 0, 1]
也就是如果匹配过程里,如果在那个位置失配,那么目标字符串就跳转到数组指定位置往下匹配而并不是从头开始, 而源字符串在失配的过程里不需要往回跳。具体还是看上面那篇文章吧,三个算法大神设计出来的算法不是三言两语能够说清楚的。
这一题基于kmp的做法是一种kmp的延伸用例。它并不需要字符串的完全匹配,它所需要的是最大部分匹配。这一点和解法一是非常类似的。
1.首先把输入中的字符串进行反转,然后以反转过的字符串作为源字符串,输入进来的字符串作为目标字符串进行kmp匹配。
2.然后设计的概念就有些取巧了。基本要做到的事情是和解法一是一样的,找到源字符串头字符起始的最长的子palindrome。但找法有些不一样,是通过逆字符串和源字符串进行部分匹配。
举个例子,如果源字符串是abcbaee,那么反转字符串就是eeabcba,然后进行以"eeabcba"为source字符串,abcbaee为target字符串的字符串匹配,前面两个e和a都不一样,所以跳过,然后最后发现source字符串中abcba的部分和target字符串前面五个abcba是部分匹配的,此时target字符串多出来的部分,就是我们要往后加的部分。这题的性质就稍微变了一下,原本我们是要在源字符串前面prepend部分字符来做一个新的palindrome字符串,但实际上我们把字符串倒转往后面append必要的最少的字符也是可以得到同样的palindrome字符串的。譬如abcbaee,这个往前填两个ee就可以得到答案eeabcbaee。但实际上先反转eeabcba后面加两个e也同样会是eeabcbaee。
给出代码如下:
public String shortestPalindrome(String s) {
if (s.length() <= 1) return s;
StringBuilder reverseBuilder = new StringBuilder(s);
String reversed = reverseBuilder.reverse().toString();
int i = 0, j = 0;
int[] pattern = buildNexts(s.toCharArray());
while (i < reversed.length() && j < s.length()) {
if (j == -1 || reversed.charAt(i) == s.charAt(j)) {
i++;
j++;
} else {
j = pattern[j];
}
}
return reversed + s.substring(j);
}
private int[] buildNexts(char[] sArr) {
int[] nextPattern = new int[sArr.length];
nextPattern[0] = -1;
nextPattern[1] = 0;
for (int i = 2; i < sArr.length; i++) {
int p = nextPattern[i - 1];
while (p >= 0 && sArr[i - 1] != sArr[p]) {
p = nextPattern[p];
}
nextPattern[i] = p + 1;
}
return nextPattern;
}
其中shortestPalindrome里面走的起始就是非常标准的kmp字符串匹配的过程,只是返回结果上有些不一样。