NC9 二叉树中是否存在节点和为指定值的路径
题目描述
给定一个二叉树和一个值 sum,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径,
方法一: 暴力递归
解题思路
因为题目中所说的路径是从根节点到叶子节点所有节点走完,而二叉树里可以用前序遍历、中序遍历、后序遍历从根节点往下一条一条地遍历到叶子节点。
我们需要做的就是在这遍历的过程中,把每个节点的值加起来,到叶子节点的时候,判断这些加起来的值是不是等于指定值就可以了。如果相等,那么我们就找到了存在这样的一条路劲;如果不相等,则不存在。
这里就以前序遍历这个方式进行说明:所有我们自己的操作都在进行左右子树遍历之前进行,即相加操作的代码都在遍历左右子树之前书写。具体思路参考下面提供的代码。
回顾整个操作,大体的思路就是找到所有的路径,再挨个判断每个路径是否符合条件,不符合就继续玩下找,直到找完了;符合就直接返回即可,无需再往下找。
以下是 targetSum 为 9 时的演示示例:
![]()
复杂度
时间复杂度:O(N)
- 递归遍历每个节点,每个节点都需要遍历访问到,总共右N个节点,时间复杂度即为O(N)。
空间复杂度:O(N)
- 递归需要用到栈结构,开辟的栈空间和树的高度有关,即O(h), h 为该二叉树的高度,即O(logN);但最坏的情况下,可以把每个节点都压入栈中,即空间复杂度可以达到 O(N)。
示例代码
public class Solution {
/**
* @param root TreeNode类
* @param sum int整型
* @return bool布尔型
*/
public boolean hasPathSum (TreeNode root, int sum) {
// 判断特殊情况
if (root == null) {
return false;
}
return preOrder(root, root.val, sum);
}
/**
* @param root 当前走到的节点,TreeNode类
* @param curSum 走到当前节点时的“节点和”
* @param targetSum “节点和”的指定值
* @return
*/
public boolean preOrder(TreeNode root, int curSum, int targetSum){
// 到达叶子节点,也就是有一条路径,之后再判断这个路径符不符合条件
if (root.left == null && root.right == null) {
// 存在节点和为指定值,符合条件
if (curSum == targetSum) {
return true;
}
return false;
}
// 递归进去防止空指针异常,加个判断
if (root.left != null) {
// 遍历左子树,看左子树有没有这样的路径
boolean b1 = preOrder(root.left, curSum+root.left.val, targetSum);
// 找到这样的路径,直接返回 true
if (b1) {
return true;
}
}
if (root.right != null) {
// 遍历右子树,看右子树有没有这样的路径
boolean b2 = preOrder(root.right, curSum+root.right.val,targetSum);
if (b2) {
return true;
}
}
// 左右子树都没有这样的路径,直接返回 false 不存在
return false;
}
} 方法二: 用栈
解题思路
之前用的是二叉树的递归遍历形式找路径。 我们知道用栈结构可以将递归方式改写为非递归方式,所以也可以使用栈结构来解决该题。
对于先序遍历,非递归的方式就是:先用栈保存头节点,因为栈结构的特性是后进先出,所以我们先压右节点,再压左节点,这样到后面弹出就是先弹左节点,再弹右节点。对于先序遍历是如此,但此题只要求我们找路径,所以先遍历哪个子树的顺序并不是关键点。
关键的地方仍然在于,在遍历的过程中,记录下每个节点的路径和,到叶子节点的时候,就判断此时的路径和是不是目标值即可。
以下是 targetSum 为 9 时的演示示例:
![]()
具体实现方式请参考代码。
复杂度
较方法一而言并没有优化多少,只是使用的栈结构是我们自己定义的而已。
时间复杂度:O(N)
- 每个节点都需要挨个访问左右节点,共有N个节点需要遍历访问到,即时间复杂度为O(N)。
空间复杂度:O(N)
- 极端情况下,一开始一直往左节点访问,它的所有右节点存储到栈空间中,此时栈空间达到最大,之后会从栈里弹出元素,所以开辟的栈空间和该二叉树的高度有关,即空间复杂度为O(h),h为该二叉树的高度,即O(logN);
但最坏的情况下,可以把每个节点都压入栈中,即空间复杂度可以达到 O(N)。
示例代码
public class Solution {
// 保存 节点 和 节点对应的路径和 的自定义类
class Node{
TreeNode node; // 节点
int sum; // 节点对应的路径和
public Node(TreeNode node, int sum){
this.node = node;
this.sum = sum;
}
}
/**
*
* @param root TreeNode类
* @param sum int整型
* @return bool布尔型
*/
public boolean hasPathSum (TreeNode root, int sum) {
// 判断特殊情况
if (root == null) {
return false;
}
// 用栈解决该问题, 栈里装的是自定义的 Node 类。
Stack<Node> stack = new Stack<>();
// 先压头节点
stack.push(new Node(root, root.val));
while (!stack.isEmpty()) {
Node popNode = stack.pop();
TreeNode node = popNode.node;
// 此时弹出的是叶子节点
if (node.left == null && node.right == null) {
// 路径和是否等于指定值
if (popNode.sum == sum) {
return true;
}
}
// 如果右孩子不为空,就压右孩子
if (node.right != null) {
// 压节点的时候, 把对应节点的路径和也记录上
stack.push( new Node(node.right, popNode.sum + node.right.val));
}
// 如果左孩子不为空,就压左孩子
if (node.left != null) {
// 压节点的时候, 把对应节点的路径和也记录上
stack.push( new Node(node.left, popNode.sum + node.left.val));
}
}
// 整个遍历过程结束,还没有找到符合条件的路径,就返回 false
return false;
}
} 
京公网安备 11010502036488号