分割回文串(中等)
DFS搜索:
先考虑最朴素的DFS搜索,需要把原串拆分成若干子串,关键点就在于:拆分点的选取,那么我们只需要去搜索拆分点即可。
class Solution {
LinkedList<String> tmp = new LinkedList<>();
List<List<String>> ans = new LinkedList<>();
public List<List<String>> partition(String s) {
dfs(s, 0, 0);
return ans;
}
void dfs(String s, int start, int len) {
if (len > s.length()) return;
if (len == s.length()) {
ans.add(new LinkedList(tmp));
return;
}
// 枚举拆分点
for (int i = start; i < s.length(); i++) {
// 枚举所有可能子串
String str = s.substring(start, i + 1);
if (check(str)) {
tmp.add(str);
dfs(s, i + 1, len + str.length());
// 回溯
tmp.removeLast();
}
}
}
boolean check(String s) {
int n = s.length();
if (n <= 1) return true;
for (int i = 0; i < n / 2; i++) {
if (s.charAt(i) != s.charAt(n - i - 1)) return false;
}
return true;
}
}
发现:每一次枚举子串时,都需要对其回文性质进行判断,考虑之前DP专题中学到的回文判断dp方法,可以先预处理得到s串中可能的回文串,再进行搜索。
class Solution {
boolean[][] dp;
LinkedList<String> tmp = new LinkedList<>();
List<List<String>> ans = new LinkedList<>();
public List<List<String>> partition(String s) {
int n = s.length();
dp = new boolean[n][n];
// 预处理s串是否为回文串
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
if (j == i || j - i == 1) dp[i][j] = true;
else dp[i][j] = dp[i + 1][j - 1] || dp[i][j];
}
}
}
// 枚举左端点
dfs(s, 0);
return ans;
}
// 在左端点的基础上,枚举右端点
void dfs(String s, int i) {
if (i > s.length()) return;
if (i == s.length()) {
// 拆分完了整个串
ans.add(new LinkedList(tmp));
return;
}
for (int j = i; j < s.length(); j++) {
if (dp[i][j]) {
// s[i..j]子串是回文串
tmp.add(s.substring(i, j + 1));
// 枚举下一个开始点
dfs(s, j + 1);
// 回溯,不选取当前的回文子串
tmp.removeLast();
}
}
}
}
分割回文串Ⅱ(困难)
dp[i],表示s[0…i]的最少拆分次数,如果s[0…i]就是回文,那么就不需要拆分,次数=0。如果不是回文,可以站在最后一个回文子串的角度进行思考,枚举s[0…i]的最后一个成立的回文子串,它们的拆分点是不同的,确定了最后一个成立的回文子串,再去看前面剩余的字符串的拆分次数即可。
class Solution {
public int minCut(String s) {
int n = s.length();
// 预处理s中的回文子串
boolean[][] is = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
if (j == i || j - i == 1) {
is[i][j] = true;
} else {
is[i][j] = is[i][j] || is[i + 1][j - 1];
}
}
}
}
int[] dp = new int[n + 1];
Arrays.fill(dp, 0x3f3f3f3f);
for (int i = 0; i < n; i++) {
if (is[0][i]) {
// 如果s[0..i]本来就是回文,那就无需进行分割
dp[i] = 0;
} else {
// 如果s[0..i]不是回文,那就需要思考最后一个回文子串的可能情况
// j=0的位置就不用考虑
for (int j = 0; j < i; j++) {
if (is[j + 1][i]) { // 最后j == i代表单独把第i个字符拆出来
// s[j+1..i]是回文
// 拆分点是s[j],还要看dp[j]的拆分情况
dp[i] = Math.min(dp[i], dp[j] + 1);
}
}
}
}
return dp[n - 1];
}
}