目录
1、回文子串("引子题")
1.1 算法原理
- 状态表示:
dp[i][j]:[i, j]区间内的子串,是否回文(i <= j)
- 状态转移方程:
s[i] == s[j]:
1. i == j --> true
2. i+1==j --> s(i) == s(j) ? true : false
3. s(i) == s(j) --> dp[i+1][j-1]
- 初始化:
无需初始化(状态转移方程的前两种情况已处理特殊的边界情况)
- 建表顺序:
从下往上(根据状态转移方程)
- 返回值:
dp表中有几个true
1.2 算法代码
class Solution {
public int countSubstrings(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
boolean[][] dp = new boolean[n][n];
int ret = 0;
// 填表 --> 从下往上
for(int i = n - 1; i >= 0; i--) {
// j >= i
for(int j = i; j < n; j++) {
if(s[i] == s[j]) {
if(i == j) dp[i][j] = true;
else if(i + 1 == j) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1];
}
if(dp[i][j]) ret++;
}
}
return ret;
}
}
2、最长回文子串
2.1 算法原理
本题算法原理与题一完全一致,最终返回最长的回文子串即可。
- 状态表示:
dp[i][j]:[i, j]区间内的子串,是否回文(i <= j)
- 状态转移方程:
s[i] == s[j]:
1. i == j --> true
2. i+1==j --> s(i) == s(j) ? true : false
3. s(i) == s(j) --> dp[i+1][j-1]
- 初始化:
无需初始化(状态转移方程的前两种情况已处理特殊的边界情况)
- 建表顺序:
从下往上(根据状态转移方程)
- 返回值:
最长回文子串
2.2 算法代码
class Solution {
public String longestPalindrome(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
boolean[][] dp = new boolean[n][n];
String ret = "";
int begin = 0;
int end = 0;
// 建表 --> 从下往上
for(int i = n - 1; i >= 0; i--) {
for(int j = i; j < n; j++) {// i <= j
if(s[i] == s[j]) {
if(i == j) dp[i][j] = true;
else if(i + 1 == j) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1];
}
if(dp[i][j]) {
// 记录最长回文子串的起始和末尾位置
if(j - i + 1 > end - begin + 1) {
begin = i;
end = j;
}
}
}
}
return ss.substring(begin, end + 1);
}
}
3、分割回文串 IV(hard)
3.1 算法原理
本题算法原理依旧是在题一判断好哪些子串是回文的基础上,分割字符串,判断是否存在三个回文串即可。
- 状态表示:
dp[i][j]:[i, j]区间内的子串,是否回文(i <= j)
- 状态转移方程:
s[i] == s[j]:
1. i == j --> true
2. i+1==j --> s(i) == s(j) ? true : false
3. s(i) == s(j) --> dp[i+1][j-1]
- 初始化:
无需初始化(状态转移方程的前两种情况已处理特殊的边界情况)
- 建表顺序:
从下往上(根据状态转移方程)
- 返回值:
是否存在三个回文串。
3.2 算法代码
class Solution {
public boolean checkPartitioning(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
boolean[][] dp = new boolean[n][n];
for(int i = n - 1; i >= 0; i--) {
for(int j = i; j < n; j++) {
if(s[i] == s[j]) {
if(i == j) dp[i][j] = true;
else if(i + 1 == j) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1];
}
}
}
// 从 i,j 位置 分割字符串
for(int i = 1; i < n; i++) {
for(int j = i; j < n - 1; j++) {
if(dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1]) return true;
}
}
return false;
}
}
4、分割字符串 II(hard)
4.1 算法原理
本题仍然在题一中 通过二维dp保存所有子串的是否回文的信息 的基础上解题。
- 状态表示:
dp[i]:以[0, i]区间内的子串,最少的分割次数
- 状态转移方程:
1. 0~i 回文 --> 0
2. 0~i 不回文 --> 0 < j < = i --> 若子串[ j, i ]回文 --> min(dp[ j - 1 ] + 1)
- 初始化:
dp表中所有元素初始化为Integer.MAX_VALUE
- 建表顺序:
从左往右
- 返回值:
dp[n-1]
4.2 算法代码
class Solution {
public int minCut(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
boolean[][] isPal = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s[i] == s[j]) {
if (i == j)
isPal[i][j] = true;
else if (i + 1 == j)
isPal[i][j] = true;
else
isPal[i][j] = isPal[i + 1][j - 1];
}
}
}
int[] dp = new int[n];
// 初始化
Arrays.fill(dp, Integer.MAX_VALUE);
for (int i = 0; i < n; i++) {
if (isPal[0][i])
dp[i] = 0;
else {
// j --> (0, i]
for (int j = 1; j <= i; j++) {
if (isPal[j][i])
dp[i] = Math.min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[n - 1];
}
}
5、最长回文子序列
5.1 算法原理
- 状态表示:
dp[i][j]:s字符串[i , j]区间内的所有子序列中,最长回文子序列的长度
- 状态转移方程:
1. s[i] == s[j]:
i==j --> 1
i+1==j --> 2
dp[i+1][j-1]+2
2. s[i] != s[j]:
max(dp[i][j-1], dp[i+1][j])
- 初始化:
无需初始化
- 建表顺序:
从下往上填写每一行,每一行从左往右填写每一列
- 返回值:
dp[0][n-1]
5.2 算法代码
class Solution {
public int longestPalindromeSubseq(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
int[][] dp = new int[n][n];
// 从下往上填每一行
// 每一行从左往右填每一列
for(int i = n - 1; i >= 0; i--) {
for(int j = i; j < n; j++) {
if(s[i] == s[j]) {
if(i == j) dp[i][j] = 1;
else if(i + 1 == j) dp[i][j] = 2;
else dp[i][j] = dp[i + 1][j - 1] + 2;
}else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
}
6、让字符串成为回文串的最少插入次数(hard)
6.1 算法原理
- 状态表示:
dp[i][j]:字符串[i, j]区间内的子串,使它成为回文串的最小插入次数
- 状态转移方程:
s[i] == s[j]:
1. i == j --> 0
2. i + 1 == j --> 0
3. dp[i + 1][j - 1]
s[i] != s[j]:
min(dp[i + 1][j], dp[i][j - 1]) + 1;
- 初始化:
无需初始化
- 建表顺序:
从上到下每一行
从左往右每一列
- 返回值:
dp[0][n-1]
6.2 算法代码
class Solution {
public int minInsertions(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
int[][] dp = new int[n][n];
// 无需初始化
// 建表 --> 从上往下每一行,从左往右每一列
for(int i = n - 1; i >= 0; i--) {
for(int j = i; j < n; j++) {
if(s[i] == s[j]) {
if(i == j) dp[i][j] = 0;
else if(i + 1 == j) dp[i][j] = 0;
else dp[i][j] = dp[i + 1][j - 1];
}else dp[i][j] = Math.min(dp[i + 1][j], dp[i][j - 1]) + 1;
}
}
return dp[0][n - 1];
}
}
END