用非常经典的一个力扣题目来理解显式回溯与隐式回溯。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<Integer> path = new ArrayList<>();
findPath(root,targetSum,path);
return res;
}
private void findPath (TreeNode root, int targetSum, List<Integer>path){
if(root == null) return;
path.add(root.val);
// System.out.println(path);
targetSum -= root.val;
// System.out.println("+" + targetSum);
if(root.left == null && root.right == null && targetSum == 0){
res.add(new ArrayList(path));
}
findPath(root.left, targetSum, path);
findPath(root.right, targetSum, path);
path.remove(path.size() - 1);
}
}
其中java是值传递,方法内的int和String之类的因此都是固定的(String是由于别的方法就算改变这个String,也是另外生成一个新的String,int则是值传递),而像ArrayList这些(比如递归回溯时候的path),值传递是一个地址,别的方法改变了,那就要回溯,否则方法栈回溯的时候,方法对应的变量就发生了变化。
所以上面的int不需要显式回溯,List需要显式回溯,且List一直在变,所以加入结果集合中必须建立一个目标副本,否则会加入一个空List。
回溯的其他思考我是看Doge Chen的博客递归回溯 值传递看懂了。
来看一下这题,下面两种回溯都是对的,可问题是为啥一个弹出两次,一个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这些可以走值传递,但是又不想发生变化时,就需要回溯。