【题目描述】输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
【解题思路】
根据路径的定义,选择树的先序遍历作为流程框架。动态保存根节点到当前节点的path。若当前节点为叶子节点,则判断路径和是否为给定的整数值。直到树的遍历结束。
【源码1】
链接:https://www.nowcoder.com/questionTerminal/b736e784e3e34731af99065031301bca
来源:牛客网
public class Solution {
private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null) return listAll;
list.add(root.val);
target -= root.val;
if(target == 0 && root.left == null && root.right == null)
listAll.add(new ArrayList<Integer>(list));
FindPath(root.left, target);
FindPath(root.right, target);
list.remove(list.size()-1);
return listAll;
}
}
a. 代码的主要思想即树的先序递归遍历,用一个arraylist来记录根节点到当前节点的路径。
b. list.remove(list.size()-1)
这句话是模拟了栈回退。当前节点为叶子节点或者已经访问过的情况下,回溯到父节点。
c. listAll.add(new ArrayList<Integer>(list))
这个细节要注意,必须要重新生成一个对象实例,并使用list对其初始化赋值。不可以直接listAll.add(list),因为list本质上是引用,在各次遍历中会直接改变它的值,最后的路径集合中前面的路径也会被后面的覆盖。再次强调这种做法是错误的。
d. 使用target递减的方式,是一个巧妙的小心思。因为可以把比较的过程直接放在遍历过程中进行。不必等到访问到叶子节点,再次对当前路径的值求和。
e. 有不少网友疑问,“栈回退以后,target不需要重新加回退节点的值吗?” 针对这个疑问,答案是不需要添加。其原理是,每次递归时,当前变量的值都会重新拷贝一份儿,进入到下次遍历中。也就是说,当前遍历过程中的各个变量值,会被保存到当前的环境中。当前遍历结束,各个变量被销毁,不会影响到回退的遍历。即遍历压栈是将当前环境压栈。
【源码2】
链接:https://www.nowcoder.com/questionTerminal/b736e784e3e34731af99065031301bca
来源:牛客网
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,
int target) {
ArrayList<ArrayList<Integer>> pathList=
new ArrayList<ArrayList<Integer>>();
if(root==null)
return pathList;
Stack<Integer> stack=new Stack<Integer>();
FindPath(root,target,stack,pathList );
return pathList;
}
private void FindPath(TreeNode root, int target,
Stack<Integer> path,
ArrayList<ArrayList<Integer>> pathList) {
if(root==null)
return;
if(root.left==null&&root.right==null){
if(root.val==target){
ArrayList<Integer> list=
new ArrayList<Integer>();
for(int i:path){
list.add(new Integer(i));
}
list.add(new Integer(root.val));
pathList.add(list);
}
}
else{
path.push(new Integer(root.val));
FindPath(root.left, target-root.val, path, pathList);
FindPath(root.right, target-root.val, path, pathList);
path.pop();
}
}
}
a. 该代码中使用了Stack来存储当前路径,更符合常规的逻辑。
b. 在存储符合条件的路径时,使用
for(int i:path){
list.add(new Integer(i));
}
是一个小技巧,避免了常规进出栈导致的复杂过程。
【情景模拟】
二叉树root, t=22
其遍历过程如下图所示:
其中,t为该位置遍历时,压栈时候的值。箭头的标号,为执行顺序。