回文串的算法问题几乎都是用动态规划解决的。
5. Longest Palindromic Substring
算法一:从对称中心扩展,算法时间复杂度
,空间复杂度为
;
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n==0)
return "";
int start = 0, end = 0;
for (int i = 0; i < 2 * n - 1; i++) {
int left = i / 2;
int right = left + i % 2;
int len=expand(s,left,right);
System.out.println(len);
if(len>end-start+1){
if(left==right){
start=left-len/2;
end=left+len/2;
}else
{
start=left-len/2+1;
end=right+len/2-1;
}
}
}
return s.substring(start,end+1);
}
public static int expand(String s, int left,int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
}
上述算法从对称中心扩展得到的回文串长度计算right - left - 1。
算法二:动态规划,时间复杂度和空间复杂度都为
dp[i][j]表示子串s[i…j]是否为回文串
public String longestPalindrome(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
int start = 0, end = 0;
for (int i = n - 1; i >= 0; i--) {
for (int j = i; i < n; j++) {
dp[i][j] = (s.charAt(i) == s.charAt(j)) && (j - i <= 2 || dp[i + 1][j - 1]);
if (dp[i][j] && j - i + 1 > end - start) {
start = i;
end = j;
}
}
}
return s.substring(start, end + 1);
}
算法三:Manacher’s Algorithm
时间和空间复杂度都为
class Solution {
public String longestPalindrome(String s) {
if(s.length()==0)
return s;
StringBuilder sb = new StringBuilder();
sb.append('^');
for (int i = 0; i < s.length(); i++)
{
sb.append('#');
sb.append(s.charAt(i));
}
sb.append("#$");
String preString = sb.toString();
int n = preString.length();
int[] P = new int[n];
int C = 0, R = 0;
for (int i = 1; i < n - 1; i++) {
int mirror = 2 * C - i;
if (i < R)
P[i] = Math.min(R - i, P[mirror]);
else
P[i] = 0;
while (preString.charAt(i + P[i] + 1) == preString.charAt(i - P[i] - 1))
P[i]++;
if (P[i] + i > R) {
C = i;
R = i + P[i];
}
}
int maxLen = 0, centre = 0;
for (int i = 1; i < n - 1; i++) {
if (maxLen < P[i]) {
maxLen = P[i];
centre = i;
}
}
int start = (centre- 1 -maxLen)/ 2 ;
return s.substring(start, start+maxLen);
}
}
516. Longest Palindromic Subsequence
算法一:暴力递归
public class LongestPalindromicSubsequence {
public Integer[][] memo;
public int longestPalindromeSubseq(String s) {
if (s.length() == 0)
return 0;
memo = new Integer[s.length()][s.length()];
return recursive(0, s.length() - 1, s);
}
public int recursive(int i, int j, String s) {
if (i > j)
return 0;
if (i == j)
return 1;
if (memo[i][j] != null)
return memo[i][j];
if (s.charAt(i) == s.charAt(j))
memo[i][j] = 2 + memo[i + 1][j - 1];
else
memo[i][j] = Math.max(recursive(i, j - 1, s), recursive(i + 1, j, s));
return memo[i][j];
}
}
算法二:二维动态规划
时间和空间复杂度都为
public static int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = 0; i < n; i++)
dp[i][i] = 1;
for (int len = 2; len <= n; len++) {
for (int start = 0; start <=n - len; start++) {
if (s.charAt(start) == s.charAt(start + len - 1))
dp[start][start + len - 1] = dp[start + 1][start + len - 2] + 2;
else
dp[start][start + len - 1] = Math.max(dp[start + 1][start + len - 1], dp[start][start + len - 2]);
}
}
return dp[0][n - 1];
}
算法三:一维动态规划
时间复杂度
,空间复杂度为
class Solution {
public static int longestPalindromeSubseq(String s) {
int[] preRow = new int[s.length()];
for (int i = s.length() - 1; i >= 0; i--) {
int[] curRow = new int[s.length()];
curRow[i] = 1;
for (int j = i + 1; j < s.length(); j++) {
int left = curRow[j - 1];
int bottom = preRow[j];
int leftBottom = preRow[j - 1];
if (s.charAt(i) == s.charAt(j))
curRow[j] = leftBottom + 2;
else
curRow[j] = Math.max(left, bottom);
}
preRow = curRow;
}
return preRow[s.length() - 1];
}
}
647. Palindromic Substrings
算法一:从对称中心扩展
class Solution {
public int countSubstrings(String s) {
int N = s.length(), ret = 0;
for (int center = 0; center < 2 * N - 1; center++)
{
/*
centre的位置既可以是字符串某个字母(回文串长度为奇数,起始left=right),也可以是间隔(回文串的长度为偶数,起始left+1=right)
012345678
m|b|a|b|m N-1+N个位置
*/
int left = center / 2;
int right = left + center % 2;
while (left >= 0 && right < N && s.charAt(left) == s.charAt(right))
{
ret++;
left--;
right++;
}
}
return ret;
}
}
算法二:Manacher’s Algorithm
class Solution {
public int countSubstrings(String s) {
StringBuilder sb = new StringBuilder();
sb.append('^');
for (int i = 0; i < s.length(); i++) {
sb.append('#');
sb.append(s.charAt(i));
}
sb.append("#$");
String preString = sb.toString();
int n = preString.length();
int[] P = new int[n];
int C = 0, R = 0;
for (int i = 1; i < n - 1; i++) {
int mirror = 2 * C - i;
if (i < R)
P[i] = Math.min(R - i, P[mirror]);
else
P[i] = 0;
while (preString.charAt(i + P[i] + 1) == preString.charAt(i - P[i] - 1))
P[i]++;
if (P[i] + i > R) {
C = i;
R = i + P[i];
}
}
int ans=0;
for(int v:P)
ans+=(v+1)/2;
return ans;
}
}