摘要
剑指 Offer 34. 二叉树中和为某一值的路径
注意到本题的要求是,找到所有满足从根节点到某个叶子节点经过的路径上的节点之和等于目标和的路径。核心思想是对树进行一次遍历,在遍历时记录从根节点到当前节点的路径和,以防止重复计算。
一、深度遍历解析
我们可以采用深度优先搜索的方式,枚举每一条从根节点到叶子节点的路径。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
算法流程:
pathSum(root, sum) 函数:
- 初始化: 结果列表 res ,路径列表 path 。
- 返回值: 返回 res 即可。
recur(root, tar) 函数:
- 递推参数: 当前节点 root ,当前目标值 tar 。
- 终止条件: 若节点 root 为空,则直接返回。
- 递推工作:
- 路径更新: 将当前节点值 root.val 加入路径 path ;
- 目标值更新: tar = tar - root.val(即目标值 tar 从 sum 减至 00 );
- 路径记录:当①root 为叶节点且② 路径和等于目标值 ,则将此路径 path 加入 res 。
- 先序遍历: 递归左 / 右子节点。
- 路径恢复: 向上回溯前,需要将当前节点从路径 path 中删除,即执行 path.pop() 。
package Tree;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
/**
* @Classname JZ34二叉树中和为某一值的路径
* @Description TODO
* @Date 2023/2/23 9:53
* @Created by xjl
*/
public class JZ34二叉树中和为某一值的路径 {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
/**
* @description 二叉树中和为某一路径的
* @param: root
* @param: target
* @date: 2023/2/23 9:54
* @return: java.util.List<java.util.List < java.lang.Integer>>
* @author: xjl
*/
// 存放的结果
List<List<Integer>> ret = new LinkedList<List<Integer>>();
// 存放的是路径
Deque<Integer> path = new LinkedList<Integer>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target);
return ret;
}
// 遍历二叉树
private void dfs(TreeNode root, int target) {
if (root == null) {
return;
}
path.offerLast(root.val);
target -= root.val;
if (root.left == null && root.right == null && target == 0) {
ret.add(new LinkedList<Integer>(path));
}
// 先添加左子树
dfs(root.left, target);
// 先添加右子树
dfs(root.right, target);
// 回溯算法进行
path.pollLast();
}
}
复杂度分析:
- 时间复杂度O(N) : N为二叉树的节点数,先序遍历需要遍历所有节点。
- 空间复杂度O(N) : 最差情况下,即树退化为链表时,path 存储所有树节点,使用O(N) 额外空间。
二、广度遍历解析
我们也可以采用广度优先搜索的方式,遍历这棵树。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
为了节省空间,我们使用哈希表记录树中的每一个节点的父节点。每次找到一个满足条件的节点,我们就从该节点出发不断向父节点迭代,即可还原出从根节点到当前节点的路径。
package Tree;
import java.util.*;
/**
* @Classname JZ34二叉树中和为某一值的路径
* @Description TODO
* @Date 2023/2/23 9:53
* @Created by xjl
*/
public class JZ34二叉树中和为某一值的路径 {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
// 存放的结果
List<List<Integer>> ret = new LinkedList<List<Integer>>();
Map<TreeNode, TreeNode> map = new HashMap<TreeNode, TreeNode>();
/**
* @description 采用的广度遍历的方式来实现
* @param: root
* @param: target
* @date: 2023/2/23 15:23
* @return: java.util.List<java.util.List<java.lang.Integer>>
* @author: xjl
*/
public List<List<Integer>> pathSum2(TreeNode root, int target) {
if (root == null) {
return ret;
}
Queue<TreeNode> queueNode = new LinkedList<TreeNode>();
Queue<Integer> queueSum = new LinkedList<Integer>();
queueNode.offer(root);
queueSum.offer(0);
while (!queueNode.isEmpty()) {
TreeNode node = queueNode.poll();
int rec = queueSum.poll() + node.val;
if (node.left == null && node.right == null && rec == target) {
getPath(node);
} else {
if (node.left != null) {
// 存储当前节点的父节点
map.put(node.left, node);
// 加入队列
queueNode.offer(node.left);
// 加入当前的值和存储在队列中
queueSum.offer(rec);
}
if (node.right != null) {
// 存储当前节点的父节点
map.put(node.right, node);
queueNode.offer(node.right);
queueSum.offer(rec);
}
}
}
return ret;
}
public void getPath(TreeNode node) {
List<Integer> temp = new LinkedList<Integer>();
//拿到当前节点加入其中,然后在只想父节点位置。
while (node != null) {
temp.add(node.val);
node = map.get(node);
}
//然后将结果翻转回来就是当前合适的路径
Collections.reverse(temp);
// 将符合的结果加入到其中。
ret.add(new LinkedList<Integer>(temp));
}
}
博文参考
《leetcode》