LeetCode深度优先算法之树(路径相关)

树的问题一般都可以由深度优先算法和广度优先算法解决,路径相关的问题一般都可以用DFS或者基于DFS实现的回溯算法实现,我们通过下面几道题来复习&训练DFS和回溯算法。

112.路径总和

描述

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例: 
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \      \
    7    2      1

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

思路

首先,题目描述的是存在从根节点到叶子节点的路径,也就是需要先确定什么情况下遍历到了叶子节点了。根据题目描述,叶子节点指的是没有左右节点的节点。

if (node.left == null && node.right == null)

sum的和与其判断的是节点的总和,所以这是一个累加的和。我们需要记录每个节点的和,先记录后遍历子节点,这种场景就是dfs典型的场景。

addVal();
recursion();

dfs可以使用递归进行实现,目前已经确定了可以使用dfs进行解决,递归的结束的条件是节点为null或者当前节点的左右节点为null。还有一个问题,我们如何确定当前叶子节点累加的值等于目标值呢?

if sum == node1.val + node2.val + node3.val;
then
   node3.val = sum - node2.val - node1.val;
 

所以我们把子节点的目标值设置为sum - curNode.val,如果curNode.val = target的话,就代表存在一条从根节点到子节点的路径和等于目标值。

代码

class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        if (root == null) return false;
        if (root.left == null && root.right == null) return sum == root.val;

        return hasPathSum(root.left, sum - root.val) ||
                hasPathSum(root.right, sum - root.val);
    }
}

113.路径总和II

题目描述

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

[
[5,4,11,2],
[5,8,4,5]
]

思路

这道题是在112.路径总和的基础上改变而来的,改变的点是要记录所有和为目标值的路径。所以,这道题不能像112题那样满足条件后直接返回boolean类型,而是要全部遍历一遍直到满足条件为止,遇到这种穷举遍历判断是否满足目标条件的情况,可以使用回溯算法来解决这个问题。

回溯本质上也是DFS,只不过是回溯一般使用一个对象进行记录节点的值,当路径节点遍历完成后,然后节点最后的值,避免分支污染。

代码

class Solution {
    
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if (root == null) return res;

        List<Integer> list = new ArrayList<>();
        this.backTrack(list, root, sum);

        return res;
    }

    void backTrack(List<Integer> list, TreeNode node, int sum) {
        if (node == null) return ;

        list.add(node.val);
        if (node.left == null && node.right == null) {
            if (node.val == sum) {
                res.add(new ArrayList<>(list));
            }
            
            list.remove(list.size() - 1);
            return;
        }

        backTrack(list, node.left, sum - node.val);
        backTrack(list, node.right, sum - node.val);

        list.remove(list.size() - 1);
    }
}

129.求根到叶子节点数字之和

题目描述

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

说明: 叶子节点是指没有子节点的节点。

示例 1:

输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.
示例 2:

输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026.

思路

透过现象看本质,[1,2,3]这种情况下,我们对应的结果是12+13=25。实际上,就是先记录左子树的值总和+右子树值总和,最后将总和值相加,这不就是后序遍历么。

recordValue();
recursion(left);
recursion(right);

既然是后序遍历,实际上也就是DFS。我们还有两个问题需要解决,第一是如果将当前节点的值进行累加,第二是递归的结束条件。

第一个问题,实际上curVal = preNum * 10 + node.val,递归过程中只要记录preNum的参数就好了,preNum的初始值就是0。

第二个问题就是递归的结束条件,其实就是遍历到叶子节点。叶子节点也就是左节点和右节点为空。

代码

class Solution {
    public int sumNumbers(TreeNode root) {
        if (root == null) return 0;
        
        return dfs(root, 0);
    }
    
    int dfs(TreeNode root, int preNum) {
        if (root == null) return 0;
        
        int num = preNum * 10 + root.val;
        if (root.left == null && root.right == null) return num;
        
        int leftSum = dfs(root.left, num);
        int rightSum = dfs(root.right, num);
        
        return leftSum + rightSum;
    }
}

257.二叉树的所有路径

题目描述


给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

思路

所有路径问题第一想法就是回溯,但是这道题不同的是存在一个特殊的字符串->,回溯删除元素的时候要考虑这种场景,所以在删除节点之前记录当前字符串的索引位置,这样就可以使用stringBuilder.deleteCharAt(index, cur.length());

代码

class Solution {
    
    List<String> res = new ArrayList<>();
    public List<String> binaryTreePaths(TreeNode root) {
        if (root == null) return res;

        this.backTrack(new StringBuilder(""), root);
        return res;
    }

    public void backTrack(StringBuilder sb, TreeNode node) {
        if (node == null) return;

        if (node.left == null && node.right == null) {
            res.add(sb.toString() + node.val);
            return;            
        }

        int delIndex = sb.length();
        sb.append(node.val).append("->");
        backTrack(sb, node.left);
        backTrack(sb, node.right);

        sb.delete(delIndex, sb.length());
    }
}

程序员面试金典04.12 求和路径

题目描述

给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。

示例:
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

3
解释:和为 22 的路径有:[5,4,11,2], [5,8,4,5], [4,11,7]
提示:

节点总数 <= 10000

思路

这道题和上面题不同的是,路径不要求是从根节点开始遍历的。所以我们需要记录每层节点的值,先通过递归算法确定树的深度,然后根据当前深度进行赋值,遍历当前深度对应值直到深度为0为止,如果遍历的和等于sum,那么数量+1

代码

class Solution {

    int res = 0;
    public int pathSum(TreeNode root, int sum) {
        if (root == null) return res;

        int depth = depth(root);
        int[] levels = new int[depth];
        this.bfs(root, levels, 0, sum);

        return res;
    }

    void bfs(TreeNode root, int[] levels, int level, int num) {
        if (root == null) return;

        levels[level] = root.val;
        int sum = 0;
        for (int i = level; i >= 0; i--) {
            sum += levels[i];
            if (sum == num) {
                res += 1;
            }
        }

        bfs(root.left, levels, level + 1, num);
        bfs(root.right, levels, level + 1, num);
    }


    int depth(TreeNode root) {
        if (root == null) return 0;

        int left = depth(root.left);
        int right = depth(root.right);
        
        return left > right ? left + 1 : right + 1;
    }
}

Hammy_0
0 声望0 粉丝