题目
输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
解析
预备知识
二叉树的前序遍历:先访问根节点,再访问左右子树。简单总结为中左右。
如图的二叉树:
前序遍历为:12345
思路一
可以尝试从上图入手这道题,假设输入的二叉树为上图描述的,目标和为6。
很明显可以看出上图中有2条路径的和为6,分别是[1,2,3],[1,5]。
根据题目的定义可以发现任何一条路径都是从根节点开始到叶子节点结束的路径。所以这时我们借助深度优先遍历来考察每条路径,当某一条路径满足条件,则把该路径添加到路径结合中。然后回溯访问下一条路径。
深度优先遍历:从某一个结点出发,访问该结点,然后继续访问该节点的孩子节点,之后继续访问孩子节点的孩子节点,直到没有孩子节点为止,然后向上回溯到上一个父节点,继续访问该节点的另一个节点。重复以上过程,不断向下递归和向上回溯,直到所有的节点访问为止。
所以我们的思路为:从根节点出发,dfs遍历二叉树,我们记录当前路径的所有节点信息和这些信息值域的总和。如果当前路径的最后一个节点为叶子节点且总和等于目标值,则把该路径加入到路径集合中。如果当前节点不是叶子节点,则继续访问它的子节点即可。每次访问某一节点的子节点后,向上回溯的时候,都要在当前路径集合中删除最后一点,且总和中也要删除该值。确保从根节点出发到当前节点的路径不包含当前节点中已考察过的孩子节点,之后我们就可以继续考察该点的另一个子节点或者向上回溯!通过这样递归和回溯的过程,必然能考虑到树的所有的路径。
public static ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null) {
return null;
}
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> temp = new ArrayList<>();
temp.add(root.val);
FindPath(result, temp, root.val, root, target);
return result;
}
/**
* dfs遍历方式,我们的根节点已经在遍历前加入
* @param result 最后的结果
* @param temp 当前的路径
* @param sum 当前的和
* @param node 路径中最后一个节点
* @param target 目标值
*/
private static void FindPath(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> temp, int sum, TreeNode node, int target) {
/*
判断是否该路径中最后一个节点是否是叶子节点
若是,判断该路径的和是否等于target
若等于,把该路径加入路径集合
*/
if(node.left == null && node.right == null && sum == target) {
ArrayList<Integer> path = new ArrayList<>();
path.addAll(temp);
result.add(new ArrayList<>(temp));
return;
}
//遍历该点的左子树
if(node.left != null) {
temp.add(node.left.val);
FindPath(result, temp, sum + node.left.val, node.left, target);
//回溯时,记得删除之前加入的点
temp.remove(temp.size() - 1);
}
//遍历该点的右子树
if(node.right != null) {
temp.add(node.right.val);
FindPath(result, temp, sum + node.right.val, node.right, target);
//回溯时,记得删除之前加入的点
temp.remove(temp.size() - 1);
}
}
思路二
我们发现路径总是从根节点出发,结合树的遍历方式来看,前序遍历非常适合于这道题。我们只需简单改写先序遍历的递归写法即可AC这道题,思路清晰,代码简洁。
我们采用先序遍历的方式遍历该二叉树,每访问一个节点,都判断该点是不是叶子节点且总和是否等于目标值,若是则加入到路径集合中。否则,继续递归访问左右子树。每次访问一个节点后,函数结束前都要做一些处理,因为我们这个节点及其左右子树都考虑完毕,这时要向上回溯到父节点。所以要在当前的路径上删除该节点信息和总和中也要删除该值。
public static ArrayList<ArrayList<Integer>> FindPath2(TreeNode root,int target) {
if(root == null) {
return null;
}
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> temp = new ArrayList<>();
FindPath(result, temp, 0, root, target);
return result;
}
/**
* 利用二叉树先序遍历的知识来做
* 每次都对根节点处理
* @param result
* @param temp
* @param sum
* @param node
* @param target
*/
private static void FindPath2(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> temp, int sum, TreeNode node, int target) {
temp.add(node.val);
sum += node.val;
if(node.left == null && node.right == null && sum == target) {
ArrayList<Integer> path = new ArrayList<>();
path.addAll(temp);
result.add(new ArrayList<>(temp));
return;
}
//遍历该点的左子树
if(node.left != null) {
FindPath2(result, temp, sum, node.left, target);
}
//遍历该点的右子树
if(node.right != null) {
FindPath2(result, temp, sum, node.right, target);
}
temp.remove(temp.size() - 1);
}
思路三
思路三实现非递归实现,这个其实有点难理解,我自己也是转了好大会。
非递归的实现其实是利用栈改写递归版本而已,只不过我们要加入一些中间值来帮助我们进行该过程。
1. 按照先序遍历的思想不断把左孩子节点加入栈中,并且记录路径信息和总和信息。
2. 每次加入后,都要判断该节点是不是叶子节点且总和等于目标值,若是,则加入路径集合中。
3. 若cur为null, 表明不能继续向下添加左孩子了,要向上回溯或者访问上一次最后遍历节点的右子树了。若有右子树,且该点不等于上一次遍历最后的节点,则继续考察右子树。
4. 我们引入last来保存上一次遍历的最后一个节点是为了右子树向父节点回溯时,防止因为第三步的判断重新考察右子树。
/**
* 非递归实现,利用last保留为上一次遍历的结点。
* 因为回溯时,右子树会回溯到父节点上,所以通过last来确定右子树是否已经访问
* @param root
* @param target
* @return
*/
public static ArrayList<ArrayList<Integer>> FindPath3(TreeNode root,int target) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(root == null) {
return result;
}
ArrayList<Integer> temp = new ArrayList<>();
TreeNode cur = root, last = null;
Stack<TreeNode> stack = new Stack<>();
int sum = 0;
while(cur != null || !stack.isEmpty()) {
if(cur == null) {
TreeNode top = stack.peek();
if(top.right != null && top.right != last) {
cur = top.right;
} else {
last = top;
stack.pop();
temp.remove(temp.size() - 1);
sum -= last.val;
}
}else {
stack.push(cur);
temp.add(cur.val);
sum += cur.val;
if(cur.left == null && cur.right == null && sum == target) {
result.add(new ArrayList<>(temp));
}
cur = cur.left;
}
}
return result;
}
总结
多考虑如何与二叉树某一种遍历联系起来。