题目描述(简单难度)

给定一个,判断是否有一条从根节点到叶子节点的路径,该路径上所有数字的和等于sum

解法一 递归

这道题其实和 是一样的,大家可以先看 111 题 的分析,这道题无非是把 递归传递的depth改为了sum的传递。

如果不仔细分析题目,代码可能会写成下边的样子。

看起来没什么问题,并且对于题目给的样例也是没问题的。但是对于下边的样例:

  1. 3
  2. / \
  3. 9 20
  4. / / \
  5. 8 15 7
  6. sum = 12

当某个子树只有一个孩子的时候,就会出问题了,可以看 111 题 的分析。

所以代码需要写成下边的样子。

  1. public boolean hasPathSum(TreeNode root, int sum) {
  2. if (root == null) {
  3. return false;
  4. }
  5. return hasPathSumHelper(root, sum);
  6. }
  7. private boolean hasPathSumHelper(TreeNode root, int sum) {
  8. //到达叶子节点
  9. if (root.left == null && root.right == null) {
  10. return root.val == sum;
  11. }
  12. //左孩子为 null
  13. if (root.left == null) {
  14. return hasPathSumHelper(root.right, sum - root.val);
  15. }
  16. //右孩子为 null
  17. if (root.right == null) {
  18. return hasPathSumHelper(root.left, sum - root.val);
  19. }
  20. return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
  21. }

解法二 BFS

同样的,我们可以利用一个队列对二叉树进行层次遍历。同时还需要一个队列,保存当前从根节点到当前节点已经累加的和。BFS的基本框架不用改变,参考 。只需要多一个队列,进行细微的改变即可。

解法三 DFS

这里的话,用DFS里的中序遍历,参考 94 题

  1. public boolean hasPathSum(TreeNode root, int sum) {
  2. Stack<TreeNode> stack = new Stack<>();
  3. Stack<Integer> stackSum = new Stack<>();
  4. TreeNode cur = root;
  5. int curSum = 0;
  6. while (cur != null || !stack.isEmpty()) {
  7. // 节点不为空一直压栈
  8. stack.push(cur);
  9. stackSum.push(curSum);
  10. cur = cur.left; // 考虑左子树
  11. }
  12. // 节点为空,就出栈
  13. cur = stack.pop();
  14. curSum = stackSum.pop();
  15. //判断是否满足条件
  16. if (curSum == sum && cur.left == null && cur.right == null) {
  17. return true;
  18. }
  19. // 考虑右子树
  20. cur = cur.right;
  21. }
  22. return false;
  23. }

但是之前讲了,对于这种利用栈完全模拟递归的思路,对时间复杂度和空间复杂度并没有什么提高。只是把递归传递的参数rootsum,本该由计算机自动的压栈出栈,由我们手动去压栈出栈了。

所以我们能不能提高一下,比如省去sum这个栈?让我们来分析以下。参考 。

我们如果只用一个变量curSum来记录根节点到当前节点累计的和,有节点入栈就加上节点的值,有节点出栈就减去节点的值。

比如对于下边的树,我们进行中序遍历。

  1. 3
  2. / \
  3. 9 20
  4. / \
  5. 8 15
  6. curSum = 0
  7. 3 入栈, curSum = 33
  8. 9 入栈, curSum = 123 -> 9
  9. 8 入栈, curSum = 20 3 -> 9 -> 8
  10. 8 出栈, curSum = 12 3 -> 9
  11. 9 出栈, curSum = 3
  12. 15 入栈, curSum = 18 3 -> 9 -> 15

此时路径是 3 -> 9 -> 15,和应该是 27。但我们得到的是 18,少加了 9

原因就是我们进行的是中序遍历,当我们还没访问右边的节点的时候,根节点已经出栈了,再访问右边节点的时候,curSum就会少一个根节点的值。

此时路径 3 -> 9 -> 15 对应的 curSum 就是正确的了。

用栈实现后序遍历,比中序遍历要复杂一些。当访问到根节点的时候,它的右子树可能访问过了,那就把根节点输出。它的右子树可能没访问过,我们需要去遍历它的右子树。所以我们要用一个变量pre保存上一次遍历的节点,用来判断当前根节点的右子树是否已经遍历完成。

  1. public List<Integer> postorderTraversal(TreeNode root) {
  2. List<Integer> result = new LinkedList<>();
  3. Stack<TreeNode> toVisit = new Stack<>();
  4. TreeNode cur = root;
  5. TreeNode pre = null;
  6. while (cur != null) {
  7. toVisit.push(cur); // 添加根节点
  8. cur = cur.left; // 递归添加左节点
  9. cur = toVisit.peek(); // 已经访问到最左的节点了
  10. // 在不存在右节点或者右节点已经访问过的情况下,访问根节点
  11. if (cur.right == null || cur.right == pre) {
  12. toVisit.pop();
  13. result.add(cur.val);
  14. pre = cur;
  15. cur = null;
  16. } else {
  17. cur = cur.right; // 右节点还没有访问过就先访问右节点
  18. }
  19. }
  20. return result;
  21. }

有了上边的后序遍历,对于这道题,代码就很好改了。

  1. public boolean hasPathSum(TreeNode root, int sum) {
  2. Stack<TreeNode> toVisit = new Stack<>();
  3. TreeNode cur = root;
  4. TreeNode pre = null;
  5. int curSum = 0; //记录当前的累计的和
  6. while (cur != null || !toVisit.isEmpty()) {
  7. while (cur != null) {
  8. toVisit.push(cur); // 添加根节点
  9. curSum += cur.val;
  10. cur = cur.left; // 递归添加左节点
  11. }
  12. cur = toVisit.peek(); // 已经访问到最左的节点了
  13. //判断是否满足条件
  14. if (curSum == sum && cur.left == null && cur.right == null) {
  15. return true;
  16. }
  17. // 在不存在右节点或者右节点已经访问过的情况下,访问根节点
  18. if (cur.right == null || cur.right == pre) {
  19. TreeNode pop = toVisit.pop();
  20. curSum -= pop.val; //减去出栈的值
  21. pre = cur;
  22. cur = null;
  23. } else {
  24. cur = cur.right; // 右节点还没有访问过就先访问右节点
  25. }
  26. }
  27. return false;

这道题还是在考二叉树的遍历,DFS,。解法三通过后序遍历节省了sum栈,蛮有意思的。

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^