题目
找出字符串中最长回文子序列,可以在原字符串中不连续。
如“character”的最长回文子序列为“carac”。
分析1
设字符串 s s s从第 i i i个字符到第 j j j个字符的最长回文子序列长度为 p [ i , j ] p[i,j] p[i,j],则递归式为
p [ i , j ] = { 1 , i = j p [ i + 1 , j − 1 ] + 2 , i ≠ j , s i = s j m a x ( p [ i + 1 , j ] , p [ i , j − 1 ] ) , i ≠ j , s i ≠ s j p[i,j]=\left\{\begin{array}{ll} 1 , & i=j\\ p[i+1,j-1]+2 , & i \neq j ,\ s_i=s_j\\ max(p[i+1,j],p[i,j-1]), & i \neq j ,\ s_i \neq s_j \end{array}\right. p[i,j]=⎩ ⎨ ⎧1,p[i+1,j−1]+2,max(p[i+1,j],p[i,j−1]),i=ji=j, si=sji=j, si=sj
基本情况是
i
=
j
i=j
i=j,即单个字符自身即为长度为1的回文序列;
当
i
≠
j
i\neq j
i=j时,
s
i
=
s
j
s_i=s_j
si=sj表示找到了可能在最长回文子序列中的字符,则最长回文子序列剩下的部分取决于除去这个字符后的序列。
当
i
≠
j
i\neq j
i=j时,
s
i
≠
s
j
s_i \neq s_j
si=sj表示当前的两个字符
s
i
s_i
si和
s
j
s_j
sj不能同时存在于最长回文子序列中,则最长回文子序列取决于在
s
i
s_i
si和
s
j
s_j
sj中删除一个字符后的两个序列中,具有较长回文子序列的那个。
代码1
import java.util.ArrayList;
import java.util.List;
public class Main {
static void palindrome(String[] s, int[][] p, String[][] so) {
int n = s.length;
for (int i = 0; i < n; i++) {
p[i][i] = 1;
}
for (int l = 2; l <= n; l++) {
for (int i = 0; i < n - l + 1; i++) {
int j = i + l - 1;
if (!s[i].equals(s[j])) {
if (p[i][j - 1] > p[i + 1][j]) {
p[i][j] = p[i][j - 1];
so[i][j] = "left";
} else {
p[i][j] = p[i + 1][j];
so[i][j] = "down";
}
} else {
p[i][j] = p[i + 1][j - 1] + 2;
so[i][j] = "leftdown";
}
}
}
}
static void solution(String[] s, String[][] so, int i, int j, List<String> result) {
if (i >= s.length || j <= 0) return;
if (i == j) {
result.add(result.size() / 2, s[i]);
return;
}
if ("leftdown".equals(so[i][j])) {
solution(s, so, i + 1, j - 1, result);
result.add(0, s[i]);
result.add(result.size(), s[i]);
} else if ("left".equals(so[i][j])) {
solution(s, so, i, j - 1, result);
} else {
solution(s, so, i + 1, j, result);
}
}
public static void main(String[] args) {
String str = "character";
String[] s = str.split("");
int n = s.length;
int[][] p = new int[n][n];
String[][] so = new String[n][n];
palindrome(s, p, so);
System.out.println(p[0][n - 1]);
List<String> result = new ArrayList<>();
solution(s, so, 0, n - 1, result);
System.out.println(result);
}
}
分析2
一个字符串的最长回文子序列就是这个字符串和其逆序串的最长公共子序列,这样就可以将问题转化。设字符串 X X X的 i i i前缀和字符串 Y Y Y的 j j j前缀的最长公共子序列长度为 p [ i , j ] p[i,j] p[i,j],则递归式为
p [ i , j ] = { 0 , i = 0 o r j = 0 p [ i − 1 , j − 1 ] + 1 , i , j ≠ 0 , X i = Y j m a x ( p [ i − 1 , j ] , p [ i , j − 1 ] ) , i , j ≠ 0 , X i ≠ Y j p[i,j]=\left\{\begin{array}{ll} 0 , & i=0\ or\ j=0\\ p[i-1,j-1]+1 , & i, j\neq 0 ,\ X_i=Y_j\\ max(p[i-1,j],p[i,j-1]), & i, j\neq 0 ,\ X_i\neq Y_j \end{array}\right. p[i,j]=⎩ ⎨ ⎧0,p[i−1,j−1]+1,max(p[i−1,j],p[i,j−1]),i=0 or j=0i,j=0, Xi=Yji,j=0, Xi=Yj
代码2
import java.util.ArrayList;
import java.util.List;
public class Main {
static void LCS(String[] X, String[] Y, int[][] p, String[][] so) {
int nX = X.length;
int nY = Y.length;
for (int i = 0; i < nX + 1; i++) {
p[i][0] = 0;
}
for (int i = 0; i < nY + 1; i++) {
p[0][i] = 0;
}
for (int i = 1; i <= nX; i++) {
for (int j = 1; j <= nY; j++) {
if (X[i - 1].equals(Y[j - 1])) {
p[i][j] = p[i - 1][j - 1] + 1;
so[i][j] = "leftup";
} else if (p[i - 1][j] >= p[i][j - 1]) {
p[i][j] = p[i - 1][j];
so[i][j] = "up";
} else {
p[i][j] = p[i][j - 1];
so[i][j] = "left";
}
}
}
}
static void solution(String[] X, String[][] so, int i, int j, List<String> result) {
if (i == 0 || j == 0) {
return;
}
if (so[i][j].equals("leftup")) {
solution(X, so, i - 1, j - 1, result);
result.add(X[i - 1]);
} else if (so[i][j].equals("up")) {
solution(X, so, i - 1, j, result);
} else {
solution(X, so, i, j - 1, result);
}
}
public static void main(String[] args) {
String str = "character";
String restr = new StringBuilder(str).reverse().toString();
String[] X = str.split("");
String[] Y = restr.split("");
int n = X.length;
int[][] p = new int[n + 1][n + 1];
String[][] so = new String[n + 1][n + 1];
LCS(X, Y, p, so);
System.out.println(p[n][n]);
List<String> result = new ArrayList<>();
solution(X, so, n, n, result);
System.out.println(result);
}
}