题目的主要信息:
- 题目给出我们一棵树的树根结点指针,和一个期待值
- 我们要找出这棵树中,从根节点到叶子节点的路径上的节点值之和等于该期待值的路径,找出所有这样的路径并返回。
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:深度优先搜索(推荐使用)
知识点:深度优先搜索(dfs)
深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。
思路:
我们从根节点开始向左右子树进行递归,递归函数中需要处理的是:
- 当前的路径
path
要更新 - 当前的目标值
expectNumber
要迭代,减去当前节点的值 - 若当前节点是叶子节点,考虑是否满足路径的期待值,并考虑是否将路径添加到返回列表中
具体做法:
- step 1:维护两个向量
ret
和path
- step 2:编写递归函数
dfs
- step 3:递归函数内部要处理更新
path
,更新expectNumber
,判断是否为叶子节点和判断是否要将path
追加到ret
末尾
图示:
Java实现代码:
import java.util.*;
public class Solution {
private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
void dfs(TreeNode root, int number) {
// 处理树为空
if (root == null) return;
// 路径更新
path.add(root.val);
// number更新
number -= root.val;
// 如果递归当前节点为叶子节点且该条路径的值已经达到了expectNumber,则更新ret
if(root.left == null && root.right == null && number == 0) {
ret.add(new ArrayList<>(path));
}
// 左右子树递归
dfs(root.left, number);
dfs(root.right, number);
path.removeLast();
}
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int expectNumber) {
dfs(root, expectNumber);
return ret;
}
}
C++实现代码:
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(TreeNode* root, int number) {
// 处理树为空
if (root == NULL) return;
// 路径更新
path.emplace_back(root->val);
// number更新
number -= root->val;
// 如果递归当前节点为叶子节点且该条路径的值已经达到了expectNumber,则更新ret
if(root->left == NULL && root->right == NULL && number == 0) {
ret.emplace_back(path);
}
// 左右子树递归
dfs(root->left, number);
dfs(root->right, number);
path.pop_back();
}
vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
dfs(root, expectNumber);
return ret;
}
};
Python实现代码:
class Solution:
def FindPath(self , root: TreeNode, target: int) -> List[List[int]]:
ret, path = [], []
def dfs(root: TreeNode, target: int):
# 处理树为空
if not root: return
# 路径更新
path.append(root.val)
# 更新
target -= root.val
# 如果递归当前节点为叶子节点且该条路径的值已经达到了expectNumber,则更新ret
if not root.left and not root.right and target == 0:
ret.append(path[:])
# 左右子树递归
dfs(root.left, target)
dfs(root.right, target)
path.pop()
dfs(root, target)
return ret
复杂度分析:
- 时间复杂度:,其中是树的节点数。最差情况下树的一半为链,一半为完全二叉树,并且所有的路径上的节点值之和都是
expectedNumber
,这样路径数量为,且每条路径的节点数也为,添加到ret
的时间代价就是 - 空间复杂度:,递归栈的深度不会超过节点数量,但最大可能就是节点数量。
方法二:广度优先遍历(扩展思路)
知识点:队列
队列是一种仅支持在表尾进行插入操作、在表头进行删除操作的线性表,插入端称为队尾,删除端称为队首,因整体类似排队的队伍而得名。它满足先进先出的性质,元素入队即将新元素加在队列的尾,元素出队即将队首元素取出,它后一个作为新的队首。
思路:
深度优先搜索是优先一条路径寻到底,而广度优先搜索是按照二叉树的层进行搜索,因此路径需要逐层进行记录,我们引入队列来维护这个逐层路径的信息。
具体做法:
- step 1:通过维护队列来进行广度优先搜索
- step 2:当我们遍历到叶子节点的层时,就进行与目标值的匹配
- step 3:最终返回目标结果
Java实现代码:
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int expectNumber) {
ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
if(root == null) return ret;
Queue<ArrayList<Integer>> pathQ = new LinkedList<ArrayList<Integer>>();
Queue<TreeNode> nodeQ = new LinkedList<TreeNode>();
pathQ.offer(new ArrayList<Integer>(Arrays.asList(root.val)));
nodeQ.offer(root);
while(!nodeQ.isEmpty()) {
ArrayList<Integer> curpath = pathQ.poll();
TreeNode curnode = nodeQ.poll();
if(curnode.left != null) {
ArrayList<Integer> left = new ArrayList<Integer>(curpath);
left.add(curnode.left.val);
pathQ.offer(left);
nodeQ.offer(curnode.left);
}
// 右子节点path
if(curnode.right != null) {
ArrayList<Integer> right = new ArrayList<Integer>(curpath);
right.add(curnode.right.val);
pathQ.offer(right);
nodeQ.offer(curnode.right);
}
// 叶子节点统计
if(curnode.left == null && curnode.right == null) {
ArrayList<Integer> l = curpath;
int sum = 0;
for(int i = 0; i < l.size(); i++) {
sum = sum + l.get(i);
}
if(sum == expectNumber) ret.add(l);
}
}
return ret;
}
}
C++实现代码:
#include<numeric>
class Solution {
public:
vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
vector<vector<int>> ret;
if(root == NULL) return ret;
pair<vector<int>, TreeNode*> p({root->val}, root);
// 维护一个path,node的队列
queue< pair<vector<int>, TreeNode*> > q;
q.push(p);
// 如果队列非空
while(!q.empty()) {
pair<vector<int>, TreeNode*> cur = q.front();
q.pop();
TreeNode* node = cur.second;
// 左子节点path
if(node->left) {
vector<int> left = cur.first;
left.push_back(node->left->val);
q.push(make_pair(left, node->left));
}
// 右子节点path
if(node->right) {
vector<int> right = cur.first;
right.push_back(node->right->val);
q.push(make_pair(right, node->right));
}
// 叶子节点统计
if(node->left == NULL && node->right == NULL) {
vector<int> l = cur.first;
int sum = accumulate(l.begin(), l.end(), 0);
if(sum == expectNumber) ret.push_back(l);
}
}
return ret;
}
};
Python实现代码:
class Solution:
def FindPath(self , root: TreeNode, target: int) -> List[List[int]]:
if not root: return []
# 直接将所需要的节点,路径,节点值组成一个列表
s = [(root, [root.val], root.val)]
ret = []
while s:
node, val, sum = s.pop()
# 如果是到了叶子节点的情况下,并且路径总和与target一致
if node and not node.left and not node.right and sum == target:
ret.append(val)
# 处理左子树路径
if node.left:
s.append((node.left, val + [node.left.val], sum + node.left.val))
# 处理右子树路径
if node.right:
s.append((node.right, val + [node.right.val], sum + node.right.val))
return ret
复杂度分析:
- 时间复杂度:最差为,分析方法与方法一相同
- 空间复杂度:,n是树的节点数量,因为一次遍历下来访问了所有节点,列表
s
的空间代价