二叉树路径问题
原文来自于 一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析)
对于刚刚接触树的问题的新手而言,路径问题是一个比较棘手的问题。题解中关于二叉树路径问题的总结还偏少,今天我用一篇文章总结一下二叉树的路径问题。学透这篇文章,二叉树路径题可以秒杀
问题分类
二叉树路径的问题大致可以分为两类:
1、自顶向下:
顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束
具体题目如下:
而继续细分的话还可以分成一般路径与给定和的路径
2、非自顶向下:
就是从任意节点到任意节点的路径,不需要自顶向下
解题模板
这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决,BFS较DFS繁琐,这里为了简洁只展现DFS代码
下面是我对两类题目的分析与模板
一、自顶而下:
dfs
一般路径: vector<vector<int>>res; void dfs(TreeNode*root,vector<int>path) { if(!root) return; //根节点为空直接返回 path.push_back(root->val); //作出选择 if(!root->left && !root->right) //如果到叶节点 { res.push_back(path); return; } dfs(root->left,path); //继续递归 dfs(root->right,path); } # **给定和的路径:** void dfs(TreeNode*root, int sum, vector<int> path) { if (!root) return; sum -= root->val; path.push_back(root->val); if (!root->left && !root->right && sum == 0) { res.push_back(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,不断比较更新全局变量即可
1 int res=0; 2 int maxPath(TreeNode *root) //以root为路径起始点的最长路径 3 { 4 if (!root) 5 return 0; 6 int left=maxPath(root->left); 7 int right=maxPath(root->right); 8 res = max(res, left + right + root->val); //更新全局变量 9 return max(left, right); //返回左右路径较长者 10 }
这类题型DFS注意点:
1、left,right代表的含义要根据题目所求设置,比如最长路径、最大路径和等等
2、全局变量res的初值设置是0还是INT_MIN要看题目节点是否存在负值,如果存在就用INT_MIN,否则就是0
3、注意两点之间路径为1,因此一个点是不能构成路径的
例题解答
一、自顶向下
257. 二叉树的所有路径
直接套用模板1即可,注意把"->"放在递归调用中
1 class Solution { 2 List<String> res = new ArrayList<>(); 3 public List<String> binaryTreePaths(TreeNode root) { 4 dfs(root,""); 5 return res; 6 } 7 8 public void dfs(TreeNode root, String s){ 9 if(root == null){ 10 return; 11 } 12 s += String.valueOf(root.val)+"->"; 13 if(root.right == null && root.left == null){ 14 s = s.substring(0, s.length()-2); 15 res.add(s); 16 } 17 dfs(root.left, s); 18 dfs(root.right, s); 19 } 20 }
1 class Solution { 2 int res = 0; 3 4 public int pathSum(TreeNode root, int sum) { 5 if (root == null){ 6 return 0; 7 } 8 dfs(root, sum); 9 pathSum(root.left, sum); 10 pathSum(root.right, sum); 11 return res; 12 } 13 14 15 public void dfs(TreeNode root, int sum){ 16 if(root == null) return; 17 sum = sum - root.val; 18 if (sum == 0){ 19 res+=1; 20 } 21 dfs(root.left, sum); 22 dfs(root.right, sum); 23 } 24 }
1 class Solution { 2 boolean res = false; 3 public boolean hasPathSum(TreeNode root, int targetSum) { 4 dfs(root, targetSum); 5 return res; 6 } 7 8 9 public void dfs(TreeNode root, int targetSum){ 10 if (root == null){ 11 return; 12 } 13 targetSum -= root.val; 14 if(root.left == null && root.right == null && targetSum == 0){ 15 res = res || true; 16 return; 17 } 18 19 dfs(root.left, targetSum); 20 dfs(root.right, targetSum); 21 } 22 }
1 class Solution { 2 3 List<List<Integer>> res = new LinkedList<>(); 4 5 public List<List<Integer>> pathSum(TreeNode root, int targetSum) { 6 dfs(root, targetSum, new LinkedList<>()); 7 return res; 8 } 9 10 public void dfs(TreeNode root, int targetSum, Deque<Integer> path){ 11 if (root == null){ 12 return; 13 } 14 path.offerLast(root.val); 15 targetSum -= root.val; 16 if(root.left == null && root.right == null && targetSum == 0){ 17 res.add(new LinkedList<Integer>(path)); 18 } 19 20 dfs(root.left, targetSum, path); 21 dfs(root.right, targetSum, path); 22 path.pollLast(); 23 } 24 }
1 class Solution { 2 int res = 0; 3 4 public int pathSum(TreeNode root, int targetSum) { 5 if(root == null){ 6 return 0; 7 } 8 dfs(root, targetSum); 9 pathSum(root.left, targetSum); 10 pathSum(root.right, targetSum); 11 return res; 12 } 13 14 public void dfs(TreeNode root, long targetSum){ 15 if (root == null){ 16 return; 17 } 18 targetSum -= root.val; 19 if(targetSum == 0){ 20 res +=1; 21 } 22 23 dfs(root.left, targetSum); 24 dfs(root.right, targetSum); 25 } 26 }
1 class Solution { 2 String dict = "abcdefghijklmnopqrstuvwxyz"; 3 String s = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; 4 public String smallestFromLeaf(TreeNode root) { 5 dfs(root, ""); 6 return s; 7 } 8 9 public void dfs(TreeNode root, String tem){ 10 if(root == null){ 11 return; 12 } 13 tem += dict.charAt(root.val); 14 if(root.left == null && root.right == null){ 15 StringBuilder sb = new StringBuilder(tem); 16 String st = sb.reverse().toString(); 17 s = s.compareTo(st)<0?s:st; 18 return; 19 } 20 dfs(root.left, tem); 21 dfs(root.right,tem); 22 } 23 }
1 class Solution { 2 String ans = "~"; 3 public String smallestFromLeaf(TreeNode root) { 4 dfs(root, new StringBuilder()); 5 return ans; 6 } 7 8 public void dfs(TreeNode node, StringBuilder sb) { 9 if (node == null) return; 10 sb.append((char)('a' + node.val)); 11 12 if (node.left == null && node.right == null) { 13 sb.reverse(); 14 String S = sb.toString(); 15 sb.reverse(); 16 17 if (S.compareTo(ans) < 0) 18 ans = S; 19 } 20 21 dfs(node.left, sb); 22 dfs(node.right, sb); 23 sb.deleteCharAt(sb.length() - 1); 24 } 25 }
注意:
最后一题放了两个解法,主要是要注意,java 是值传递,当dfs() 函数的参数 是一个对象的时候,地址值会被复制到函数中,在
一题中,如果不加上 “path.pollLast();” 最终结果 path 的长度会出现问题,为什么String 类型不会出问题呢?因为String 不可变 每次修改都是 new String(), 同理StringBuilder 就需要“先增后减”;
二、非自顶向下
124. 二叉树中的最大路径和
left,right分别为根节点左右子树最大路径和,注意:如果最大路径和<0,意味着该路径和对总路径和做负贡献,因此不要计入到总路径中,将它设置为0
1 class Solution { 2 Integer res = Integer.MIN_VALUE; 3 public int maxPathSum(TreeNode root) { 4 maxPath(root); 5 return res; 6 } 7 8 int maxPath(TreeNode root){ 9 if(root == null){ 10 return 0; 11 } 12 int left = Math.max(0, maxPath(root.left)); 13 int right = Math.max(0, maxPath(root.right)); 14 // 比较当前最大路径和与左右子树最长路径加上根节点值的较大值,更新全局变量 15 res = Math.max(res, left+right+root.val); 16 // 返回左右子树较长的路径加上根节点值 17 return Math.max(left+root.val, right+root.val); 18 } 19 }
687. 最长同值路径
1 class Solution { 2 int res = 0; 3 public int longestUnivaluePath(TreeNode root) { 4 dfs(root); 5 return res; 6 } 7 8 public int dfs(TreeNode root){ 9 if(root == null){ 10 return 0; 11 } 12 int left = dfs(root.left); 13 int right = dfs(root.right); 14 // 如果存在左子节点和根节点同值,更新左最长路径;否则左最长路径为0 15 if(root.left !=null && root.val == root.left.val){ 16 left++; 17 }else{ 18 left = 0; 19 } 20 21 if(root.right !=null && root.val == root.right.val){ 22 right++; 23 }else{ 24 right = 0; 25 } 26 27 res = Math.max(res, left+right); 28 return Math.max(left, right); 29 30 } 31 }
543. 二叉树的直径
1 class Solution { 2 int res = 0; 3 public int diameterOfBinaryTree(TreeNode root) { 4 maxPath(root); 5 return res; 6 } 7 8 int maxPath(TreeNode root){ 9 // 这里递归结束条件要特别注意:不能是!root(而且不需要判断root为空,因为只有非空才会进入递归),因为单个节点路径长也是0 10 if (root.left == null && root.right == null) 11 return 0; 12 int left = root.left == null ? 0 : maxPath(root.left) +1; 13 int right = root.right == null ? 0: maxPath(root.right)+1; 14 res = Math.max(res, left+right); 15 return Math.max(right, left); 16 } 17 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构