在写算法题的时候,犯了一些错误,突入忘记了java是值传递,方法内的int和String之类的因此都是固定的(String是由于别的方法就算改变这个String,也是另外生成一个新的String,int则是值传递),而像ArrayList这些(比如递归回溯时候的path),值传递是一个地址,别的方法改变了,那就要回溯,否则方法栈回溯的时候,方法对应的变量就发生了变化。
回溯位置
关于回溯位置是在for循环内还是外,看他改变路径的地方,其实平常完整执行完一个方法,你方法内,写了一个改变路径和一个回溯路径,两两抵消,写哪都没事,但是最后一次你的代码加入结果的时候(第一次也是,可能到下一个选择执行的时候还没执行到回溯),可能这两步就执行了一步,那么就有问题了,所以我们分析的时候,看看最后一步会不会只加不减,那就会有问题,都不加不减或者加了也减了就没事。或者看下一个选择的时候,这时候路径对不对,有没有回溯正确,分析一下上一个选择。
路径总和
来看一下这题,下面两种回溯都是对的,可问题是为啥一个弹出两次,一个if一次,而下面的java代码只要最后写一次,其实问题就出在最后一次加入结果集的时候,上面的代码执行到叶子节点时候,明显不会执行下面的if语句里面的弹出回溯,而java的则会执行一次回溯。重点还是分析是不是有进有出,进出的总次数,主要分析第一次和最后一次
if(root->left!=NULL) {
dfs(root->left, sum);
path.pop_back();
}
if(root->right!=NULL) {
dfs(root->right, sum);
path.pop_back();
}
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<Integer> path = new ArrayList<>();
findPath(root,path,targetSum);
return res;
}
public void findPath(TreeNode root,List<Integer> path,int targetSum){
if(root == null){
return;
}
path.add(root.val);
if(root.left == null && root.right == null && targetSum == root.val){
res.add(new ArrayList(path));
}
findPath(root.left,path,targetSum - root.val);
findPath(root.right,path,targetSum - root.val);
path.remove(path.size()-1);
}
}
总结:
String在同一个方法内引用的内容固定,也就是递归下去的两个方法内参数内容是固定的,参见隐式回溯代码,不需要进行直接的回溯,两个方法内参数依然是那个String,而这题需要String发生了改变,同一个方法内第二次调用时候,String应该已经减去许多,这时候需要发生变化就定义一个全局变量,不走方法内值传递。走的是全局引用,一旦发生改变,就是新创建一个对象,引用的指向也发生改变。
而ArrayList这些可以走值传递,但是又不想发生变化时,就需要回溯。
序列化与反序列化二叉树
如果把String带入参数,那么 t.left = Deserialize(); t.right = Deserialize();方法内应该含有String,这时候就会出现左右子树使用的String一样。
private String deserializeStr;
public String Serialize(TreeNode root) {
if (root == null)
return "#";
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
}
public TreeNode Deserialize(String str) {
deserializeStr = str;
return Deserialize();
}
private TreeNode Deserialize() {
if (deserializeStr.length() == 0)
return null;
int index = deserializeStr.indexOf(" ");
String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index);
deserializeStr = index == -1 ? "" : deserializeStr.substring(index + 1);
if (node.equals("#"))
return null;
int val = Integer.valueOf(node);
TreeNode t = new TreeNode(val);
t.left = Deserialize();
t.right = Deserialize();
return t;
}
隐式回溯 二叉树的所有路径
class Solution {
List<String> res = new ArrayList<String>();
public List<String> binaryTreePaths(TreeNode root) {
backtracking(root,"");
return res;
}
public void backtracking(TreeNode root, String path){
if(root == null){
return;
}
StringBuffer pathSB = new StringBuffer(path);
pathSB.append(Integer.toString(root.val));
if(root.left == null && root.right == null){
res.add(pathSB.toString());
//最后一次可不需要再执行下面的内容了,当然这题执行也没事,不涉及到路径的回溯,因为每个方法内都是新的StringBuffer
return;
}
pathSB.append("->");
//后续的改变是新的StringBuffer,效果也是一样,不需要回溯
backtracking(root.left,pathSB.toString());
backtracking(root.right,pathSB.toString());
}
}
分割回文串
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
int len = s.length();
//判断是否回文串dp
boolean[][] dp = new boolean[len][len];
for(int i = len - 1; i >= 0; i--){
for(int j = i; j <= len - 1; j++){
if(s.charAt(i) == s.charAt(j)){
if(j - i <= 1){
dp[i][j] = true;
}else if(dp[i+1][j-1]){
dp[i][j] = true;
}
}
}
}
backtracking(s,0,dp);
return res;
}
public void backtracking(String s, int startIndex, boolean[][] dp){
if(startIndex >= s.length()){
res.add(new ArrayList(path));
}
for(int i = startIndex; i < s.length(); i++){
//看看是不是回文串,是的话才能开启下一层
if(!dp[startIndex][i]){
continue;
}
path.add(s.substring(startIndex,i + 1));
backtracking(s,i + 1,dp);
path.remove(path.size() - 1);
}
}
}