题目描述(简单难度)

112. Path Sum - 图1

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

解法一 递归

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

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

  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. if (root == null) {
  9. return sum == 0;
  10. }
  11. return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
  12. }

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

  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的基本框架不用改变,参考 102 题。只需要多一个队列,进行细微的改变即可。

  1. public boolean hasPathSum(TreeNode root, int sum) {
  2. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  3. Queue<Integer> queueSum = new LinkedList<Integer>();
  4. if (root == null)
  5. return false;
  6. queue.offer(root);
  7. queueSum.offer(root.val);
  8. while (!queue.isEmpty()) {
  9. int levelNum = queue.size(); // 当前层元素的个数
  10. for (int i = 0; i < levelNum; i++) {
  11. TreeNode curNode = queue.poll();
  12. int curSum = queueSum.poll();
  13. if (curNode != null) {
  14. //判断叶子节点是否满足了条件
  15. if (curNode.left == null && curNode.right == null && curSum == sum) {
  16. return true;
  17. }
  18. //当前节点和累计的和加入队列
  19. if (curNode.left != null) {
  20. queue.offer(curNode.left);
  21. queueSum.offer(curSum + curNode.left.val);
  22. }
  23. if (curNode.right != null) {
  24. queue.offer(curNode.right);
  25. queueSum.offer(curSum + curNode.right.val);
  26. }
  27. }
  28. }
  29. }
  30. return false;
  31. }

解法三 DFS

解法一其实本质上就是做了DFS,我们知道DFS可以用栈去模拟。对于这道题,我们可以像解法二的BFS一样,再增加一个栈,去保存从根节点到当前节点累计的和就可以了。

这里的话,用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. while (cur != null) {
  9. stack.push(cur);
  10. curSum += cur.val;
  11. stackSum.push(curSum);
  12. cur = cur.left; // 考虑左子树
  13. }
  14. // 节点为空,就出栈
  15. cur = stack.pop();
  16. curSum = stackSum.pop();
  17. //判断是否满足条件
  18. if (curSum == sum && cur.left == null && cur.right == null) {
  19. return true;
  20. }
  21. // 考虑右子树
  22. cur = cur.right;
  23. }
  24. return false;
  25. }

但是之前讲了,对于这种利用栈完全模拟递归的思路,对时间复杂度和空间复杂度并没有什么提高。只是把递归传递的参数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就会少一个根节点的值。

所以,我们可以用后序遍历,先访问左子树,再访问右子树,最后访问根节点。再看一下上边的问题。

  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. 15 入栈, curSum = 27 3 -> 9 -> 15

此时路径 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 || !toVisit.isEmpty()) {
  7. while (cur != null) {
  8. toVisit.push(cur); // 添加根节点
  9. cur = cur.left; // 递归添加左节点
  10. }
  11. cur = toVisit.peek(); // 已经访问到最左的节点了
  12. // 在不存在右节点或者右节点已经访问过的情况下,访问根节点
  13. if (cur.right == null || cur.right == pre) {
  14. toVisit.pop();
  15. result.add(cur.val);
  16. pre = cur;
  17. cur = null;
  18. } else {
  19. cur = cur.right; // 右节点还没有访问过就先访问右节点
  20. }
  21. }
  22. return result;
  23. }

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

  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;
  28. }

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

windliang wechat

添加好友一起进步~

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

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情