迴文串問題是一個常見的問題,迴文串就是一個字符串頭尾指針向中間移動時每一時刻指針指向的字符都相等,比如aabbaa。
判斷一個迴文串,最簡單的方法就是如定義一樣,給一個頭尾指針,向中間移動判斷就行,這裏不贅述。
比較巧妙的是利用迴文串的性質去解題。
最長迴文子串問題
先看一道題目,地址:
https://leetcode.com/problems/longest-palindromic-substring/
求一個字符串中的最長迴文子串,比如babad,其中最長子串是bab。
解決這個問題,最直觀的方法是找出所有子串,判斷每一個是不是迴文串,基本上是o(n3)的複雜度。
其實,看一下回文子串是如何生成的,比如有一個cdadc,先看中間的a,對一個空串和單個字符都認爲是一個迴文子串,那麼a兩邊的字符d相等,則可以生成一個長度爲3的迴文子串。
我們令一個布爾數組dp[i][j]爲子串(i,j)是否是迴文子串。解題的步驟是,設置所有長度爲1的子串dp[i][i]以及空串的值爲true,然後有遞推關係:
if(str[i] == str[j]) dp[i][j] = dp[i+1][j-1];
可以得到如下代碼,複雜度n2:
public class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length()<2) return s;
int n = s.length(),maxLen = 0;
int l=0,r = 1;
//dp[i][j]表示子串i,j是否是迴文字符串。
boolean[][] dp = new boolean[n][n];
//初始化單字符串和空串均爲迴文字符串
for (int i = 0;i<n;i++) {
for (int j = i;j>=0;j--) {
dp[i][j] = true;
}
}
//len是將要分析的迴文字符串長度,i是起始位置
for (int len = 1;len<n;len++) {
for (int i = 0;i+len<n;i++) {
int j = i + len;
if (s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i+1][j-1];
if (dp[i][j]) {
if (j - i + 1 > maxLen) {
maxLen = j - i +1;
l = i;
r = j+1;
}
}
}else {
dp[i][j] = false;
}
}
}
return s.substring(l,r);
}
}
最少迴文切割問題
題目地址:
https://leetcode.com/problems/palindrome-partitioning-ii/
Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s = “aab”,
Return 1 since the palindrome partitioning [“aa”,”b”] could be produced using 1 cut.
大致同上題的思路。不過稍微的變換了一下dp的順序。
記(j,i)爲一個子串,pal[i][j]爲子串i,j是否爲迴文。cut[i]爲子串0,i 的最小切割次數。注意一下動態規劃的順序,比如計算到子串(0,i)的前一個是子串(0,i-1),在計算(0,i-1)時候我們依次考察了子串(0,i-1),(1,i-1),(2,i-1)….(i-1,i-1)。計算(0,i)時,我們需要的全部中間結果都在上一輪中進行了計算。並擁有局部最優解。
代碼如下:
public class Solution {
public int minCut(String s) {
if (s == null || s.length() == 0) return 0;
boolean[][] pal = new boolean[s.length()][s.length()];
int[] cut = new int[s.length()];
for (int i = 0;i<s.length();i++) {
int min = i;
for (int j = 0;j <= i;j++) {
//j+1 > i - 1即爲空串,空串認爲是迴文。
if (s.charAt(i) == s.charAt(j) && (j + 1 > i - 1 || pal[j + 1][i - 1])) {
//當前子串(j,i)如果是迴文,那麼子串(0,j-1),(j,i)組成子串(0,i)。
//子串(0,i)最小切割次數就是上一次切割次數cut[j-1]+1((j,i)爲迴文子串,只多一次切割).
pal[j][i] = true;
min = j == 0 ? 0 : Math.min(cut[j - 1] + 1, min);
}
}
cut[i] = min;
}
return cut[s.length() - 1];
}
}
最短迴文前綴問題
題目地址:
https://leetcode.com/problems/shortest-palindrome/
將一個字符串轉化爲迴文字符串,只能在前面插入字符,求插入的最短字符。 比如aacecaaa,左邊插入一個a即可。
這個問題,可以轉化爲求當前字符串的最長迴文前綴。比如例子中 aacecaa爲一個迴文前綴,那麼後面在向左邊插入字符並和最右邊非迴文部分對應,組成一個迴文即可。
如何求迴文前綴?還是,針對迴文串左右對應的性質,比如給定一個字符串s,我們把s翻轉成s’並得到一個新的字符串: s+”#”+s’,比如acadf,生成新字符串 acadf#fdaca,看這個時候我們求的最長公共前後綴長度就是最長迴文前綴長度。添加”#”的作用時防治越界,原數組構建數組傻傻分不清楚。用一個不存在於原字符串的字符隔開即可。這樣最大前後綴的值不超過原字符串的長度。
找最長迴文前綴,可以看一下KMP算法:
http://blog.csdn.net/yy254117440/article/details/53129175
代碼如下:
public class Solution {
//問題可轉化爲從左邊尋找最長迴文字符前綴。添加一個後置的倒轉字符串,可轉換爲尋找最長公共前後綴。
//KMP算法,添加一個#防止在尋找最大公共前後綴時越界
public String shortestPalindrome(String s) {
int next[] = getNext(s +"#" +new StringBuilder(s).reverse());
StringBuilder sb = new StringBuilder();
int len = next[s.length()*2 + 1];
for (int i = s.length() -1; i >= len ;i--) {
sb.append(s.charAt(i));
}
return sb.toString()+s;
}
private int[] getNext(String s) {
int[] next = new int[s.length() + 1];
next[0] = 0;
next[1] = 0;
int j = 0;
for (int i = 1;i<s.length();i++) {
while (j>0 && s.charAt(i) != s.charAt(j)) j = next[j];
if (s.charAt(i) == s.charAt(j)) j ++;
next[i+1] = j;
}
return next;
}
}
Manacher’s Algorithm
解決迴文串問題,還有一個複雜度爲O(N)的算法
放一下該算法的參考地址:
http://articles.leetcode.com/longest-palindromic-substring-part-ii/
https://www.felix021.com/blog/read.php?2040