回溯模板题
题目:
131. 分割回文串
难度中等
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16s仅由小写英文字母组成
首先写下回溯模板:
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
backroll();
return res;
}
void backroll(int start,List<List<String>> result){
if(){
result.add();
return;
}
for(){
//操作
//递归
//撤销操作
}
}
}
看题目,要求分割回文串的方案,如何判断一个字符串是否为回文串?
这个回文串是镶嵌在for()里面的,需要循环很多次,所以不能每次都调用一个函数单独判断,所以要用到动态规划,也就是dp[i][j],表示s[i,j]是否是回文串。注意条件也即状态转移方程:s.charAt(i) == s.charAt(j) && (j-i <= 2 || dp[i+1][j-1] == 1的时候,dp[i][j]就为1(为回文串),j-i <= 2表示当前区间除去最外层的i和j,只有一个字符,当然是回文串了,如aba。
int len = s.length();
int[][] dp = new int[len][len];
for(int right = 0;right < len;right++){
for(int left = 0;left <= right;left++){
if(s.charAt(left) == s.charAt(right) && (right-left <= 2 || dp[left+1][right-1] == 1)){
dp[left][right] = 1;
}
}
}
上面还有个模板是让一个区间在数组里面遍历的办法:先right再left,并且取名不容易搞混。left <= right是为了让一个字符也能进入回文串判断逻辑,毕竟有right-left <= 2可以让它过。
剩下就是模板具体化:
class Solution {
public List<List<String>> partition(String s) {
//不用动态规划的话,回溯判断回文串有点难受,所以要动规减少回文串判断的时间复杂度
List<List<String>> res = new ArrayList<>();
int len = s.length();
int[][] dp = new int[len][len];
for(int right = 0;right < len;right++){
for(int left = 0;left <= right;left++){
if(s.charAt(left) == s.charAt(right) && (right-left <= 2 || dp[left+1][right-1] == 1)){
dp[left][right] = 1;
}
}
}
backroll(0,res,new ArrayList<>(),s,dp);
return res;
}
void backroll(int start,List<List<String>> result,List<String> templist,String s,int[][] dp){
if(start == s.length()){
result.add(new ArrayList<>(templist));
return;
}
for(int i = start;i < s.length();i++){
if(dp[start][i] == 1){
templist.add(s.substring(start,i+1));
backroll(i+1,result,templist,s,dp);
templist.remove(templist.size()-1);
}
}
}
}
最后是啰嗦注释版:
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
int len = s.length();
//dp
int[][] dp = new int[len][len];
for(int right = 0;right < len;right++){
for(int left = 0;left <= right;left++){
if(s.charAt(left) == s.charAt(right) && (right - left <= 2 || dp[left+1][right-1] == 1)){
dp[left][right] = 1;
}
}
}
backroll(0,result,new ArrayList<>(),s,dp);
return result;
}
void backroll(int start,List<List<String>> result,List<String> templist,String s,int[][] dp){
if(start == s.length()){//一轮遍历已经过了,一组方案就结束了,如果没有一组符合就会add []进去(实际不可能,毕竟'a'也是回文串,而且题目说了1<=s.length不会为空字符串)
result.add(new ArrayList<>(templist));
return;
}
for(int i = start;i < s.length();i++){//因为你每次都不能从头遍历,需要接着上一次递归的地方往后遍历寻找回文串,所以i=start
if(dp[start][i] == 1){//只有回文串才有资格进入我们的方案,这里也可以调用判断s.substring(start,i+1)是否为回文串,但是这样复杂度是一个0(n),递归本身很吃内存,然后又是O(N^2)时间复杂度......稍显不妥,所以需要dp优化,也即查表法。
templist.add(s.substring(start,i+1));//substring(i,j)是截取s的[i,j)区间!
backroll(i+1,result,templist,s,dp);
templist.remove(templist.size()-1);//回去分支做选择
}
}
}
}

浙公网安备 33010602011771号