二叉树路径的问题大致可以分为两类:
1. 自顶向下: 顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束 具体题目如下:
2. 非自顶向下: 就是从任意节点到任意节点的路径,不需要自顶向下
解题模板 这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决,BFS较DFS繁琐,这里为了简洁只展现DFS代码 下面是我对两类题目的分析与模板
一、自顶而下: dfs
//一般路径:
List<List<Integer>> res;
void dfs(TreeNode root,List<Integer> path)
{
if(root==null) return; //根节点为空直接返回
path.add(root.val); //作出选择
if(root.left==null && root.right==null) //如果到叶节点
{
res.add(path);
return;
}
dfs(root.left,path); //继续递归
dfs(root.right,path);
}
//给定和的路径:
void dfs(TreeNode root, int sum, List<Integer> path)
{
if (root==null) return;
sum -= root.val;
path.add(root.val);
if (root.left==null && root.right==null && sum == 0)
{
res.add(path);
return;
}
dfs(root.left, sum, path);
dfs(root.right, sum, path);
}
这类题型DFS注意点:
1. 如果是找路径和等于给定target的路径的,那么可以不用新增一个临时变量cursum来判断当前路径和, 只需要用给定和target减去节点值,最终结束条件判断target==0即可
2. 是否要回溯:二叉树的问题大部分是不需要回溯的,原因如下: 二叉树的递归部分:dfs(root.left),dfs(root.right)已经把可能的路径穷尽了, 因此到任意叶节点的路径只可能有一条,绝对不可能出现另外的路径也到这个满足条件的叶节点的;
而对比二维数组(例如迷宫问题)的DFS,for循环向四个方向查找每次只能朝向一个方向,并没有穷尽路径, 因此某一个满足条件的点可能是有多条路径到该点的
并且visited数组标记已经走过的路径是会受到另外路径是否访问的影响,这时候必须回溯
3. 找到路径后是否要return: 取决于题目是否要求找到叶节点满足条件的路径,如果必须到叶节点,那么就要return; 但如果是到任意节点都可以,那么必不能return,因为这条路径下面还可能有更深的路径满足条件,还要在此基础上继续递归
4. 是否要双重递归(即调用根节点的dfs函数后,继续调用根左右节点的pathsum函数):看题目要不要求从根节点开始的,还是从任意节点开始
二、非自顶而下:
这类题目一般解题思路如下: 设计一个辅助函数maxpath,调用自身求出以一个节点为根节点的左侧最长路径left和右侧最长路径right,那么经过该节点的最长路径就是left+right 接着只需要从根节点开始dfs,不断比较更新全局变量即可
int res=0;
int maxPath(TreeNode root) //以root为路径起始点的最长路径
{
if (root==null)
return 0;
int left=maxPath(root.left);
int right=maxPath(root.right);
res = Math.max(res, left + right + root.val); //更新全局变量
return Math.max(left, right); //返回左右路径较长者
}
这类题型DFS注意点:
1、left,right代表的含义要根据题目所求设置,比如最长路径、最大路径和等等
2、全局变量res的初值设置是0还是INT_MIN要看题目节点是否存在负值,如果存在就用INT_MIN,否则就是0
3、注意两点之间路径为1,因此一个点是不能构成路径的
题目分析 下面是对具体题目的分析和代码呈现 一、自顶向下 257. 二叉树的所有路径 直接套用模板1即可,注意把"->"放在递归调用中
List<string> res;
List<string> binaryTreePaths(TreeNode<T> root)
{
dfs(root, "");
return res;
}
void dfs(TreeNode root, string path)
{
if (root==null)
return;
path += String.valueOf(root.val);
if (root.left==null && root.right==null)
{
res.add(path);
return;
}
dfs(root.left, path+"->");
dfs(root.right, path+"->");
}
113. 路径总和 II 直接套用模板2
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
Deque<Integer> path=new LinkedList<>();
dfs(root,targetSum,path);
return res;
}
void dfs(TreeNode node,int sum,Deque<Integer> path){
if(node==null) return;
path.offerLast(node.val);
sum-=node.val;
if(node.right==null&&node.left==null&&sum==0){
res.add(new LinkedList<Integer>(path));
}
dfs(node.left,sum,path);
dfs(node.right,sum,path);
path.pollLast();
}
437. 路径总和 III 双重递归:先调用dfs函数从root开始查找路径,再调用pathsum函数到root左右子树开始查找 套用模板2
int count = 0;
int pathSum(TreeNode root, int targetSum)
{
if (root==null)
return 0;
dfs(root, targetSum); //以root为起始点查找路径
pathSum(root.left, targetSum); //左子树递归
pathSum(root.right, targetSum); //右子树递归
return count;
}
void dfs(TreeNode root, long sum)
{
if (root==null)
return;
sum -= root.val;
if (sum == 0) //注意不要return,因为不要求到叶节点结束,所以一条路径下面还可能有另一条
count++; //如果找到了一个路径全局变量就+1
dfs(root.left, sum);
dfs(root.right, sum);
}
List<String> path=new ArrayList<>();
public String smallestFromLeaf(TreeNode root) {
dfs(root, "");
Collections.sort(path); //升序排序
return path.get(0);
}
void dfs(TreeNode root, String s)
{
if (root==null)
return;
s+=((char)('a'+root.val));
if (root.left==null&&root.right==null){
String reverse = new StringBuffer(s).reverse().toString();
path.add(reverse);
return;
}
dfs(root.left, s);
dfs(root.right, s);
}
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxPath(root);
return res;
}
int maxPath(TreeNode root){
//以root为起点最长路径
if(root==null) return 0;
int left=Math.max(maxPath(root.left),0);
int right=Math.max(maxPath(root.right),0);
res=Math.max(res,left+right+root.val);
return Math.max(left+root.val,right+root.val);
}
687. 最长同值路径
int res;
public int longestUnivaluePath(TreeNode root) {
Path(root);
return res;
}
private int Path(TreeNode root){
if(root==null) return 0;
int left=Path(root.left);
int right=Path(root.right);
if(root.left!=null&&root.val==root.left.val){
left++;
}else{
left=0;
}
if(root.right!=null&&root.val==root.right.val){
right++;
}else{
right=0;
}
res=Math.max(res,left+right);
return Math.max(left,right);
}
int res;
public int diameterOfBinaryTree(TreeNode root) {
diameter(root);
return res;
}
public int diameter(TreeNode root){
if(root==null) return 0;
int left=diameter(root.left);
int right=diameter(root.right);
res=Math.max(res,left+right);
return Math.max(left,right)+1;
}
112.路径总和
DFS
首先是 DFS 解法,该解法的想法是一直向下找到 叶子节点,如果到 叶子节点 时 sum == 0
,说明找到了一条符合要求的路径。
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
if(root.left==null&&root.right==null) return root.val==targetSum;
return hasPathSum(root.left,targetSum-root.val)||hasPathSum(root.right,targetSum-root.val);
}
437. 路径总和 III 双重递归:先调用dfs函数从root开始查找路径,再调用pathsum函数到root左右子树开始查找 套用模板2
回溯
这里的回溯指 利用 DFS 找出从根节点到叶子节点的所有路径,只要有任意一条路径的 和 等于 sum
,就返回 True
。
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(0, targetSum, root);
}
public boolean dfs(int sum, int target, TreeNode root){
if (root==null){
return false;
}
if (root.left==null && root.right == null) return target==sum+root.val;
boolean dfsleft = false;
if (root.left!=null){
dfsleft = dfs(sum+root.val, target, root.left);
}
boolean dfsright = false;
if (!dfsleft && root.right != null ){
dfsright = dfs(sum+root.val, target, root.right);
}
// 并且代表遍历到根节点
return dfsleft || dfsright;
}
BFS
BFS 使用 队列 保存遍历到每个节点时的路径和,如果该节点恰好是叶子节点,并且 路径和 正好等于 sum
,说明找到了解。
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root==null) return false;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
LinkedList<Integer> sumQueue = new LinkedList<>();
sumQueue.offer(0);
while (queue.size()>0){
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode poll = queue.poll();
Integer sum = sumQueue.poll();
if (poll.left==null && poll.right==null && (poll.val+sum) == targetSum){
return true;
}
if (poll.left!=null){
queue.offer(poll.left);
sumQueue.offer(sum+poll.val);
}
if (poll.right!=null){
queue.offer(poll.right);
sumQueue.offer(sum+poll.val);
}
}
}
return false;
}
栈
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if(root == null) return false;
stack<treenode> stack1 = new stack<>();
stack<integer> stack2 = new stack<>();
stack1.push(root);
stack2.push(root.val);
while(!stack1.isempty()) {
int size = stack1.size();
for(int i = 0; i < size; i++) {
treenode node = stack1.pop();
int sum = stack2.pop();
// 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
if(node.left == null && node.right == null && sum == targetsum) {
return true;
}
// 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
if(node.right != null){
stack1.push(node.right);
stack2.push(sum + node.right.val);
}
// 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来
if(node.left != null) {
stack1.push(node.left);
stack2.push(sum + node.left.val);
}
}
}
return false;
}
}
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。