我的刷题之旅 —— 二叉树

    科技2022-08-10  110

    我的刷题之旅 —— 二叉树

    第一章 链表 第二章 二叉树 第三章 栈、堆和队列 第四章 哈希表 第五章 字符串 第六章 数组和矩阵(不是重点) 第七章 图 第八章 位运算 算法——1 双指针 算法——2 排序 算法——3 贪心思想 算法——4 分治(含二分) 算法——5 搜索(含深搜宽搜) 算法——6 动态规划 算法——7 数学 算法——8 并查集


    目录

    我的刷题之旅 —— 二叉树前言(一) 遍历及相关题目(前中序)其实本质还是深搜DFS,看你怎么想了。94 二叉树的中序遍历105 前序遍历和中序遍历的结果构造出二叉树——分治法538 把二叉搜索树转化为累加树(变形遍历题)剑指offer54 求BST倒数第k大节点 (二)BFS和DFS102二叉树的层序遍历——BFS104 二叉树的最大深度——BFS和DFS都可以617 合并二叉树——DFS226 翻转二叉树——DFS(前序可以 后序也可以)101 对称二叉树——DFS和BFS236 二叉树的最近公共祖先——DFS(后序)98 验证二叉搜索树——DFS(中序遍历或者定义)114 二叉树展开为链表(后序或者迭代)297 二叉树的序列化与反序列化(BFS序列化BFS反序列化) (三)路径相关112 路径总和(BFS 递归都可以)113 路径总和II(DFS递归)543 求二叉树最大直径——DFS124 二叉树两点间最大路径和687最长同值路径 (四)二叉树的动态规划337 打家劫舍III96 不同的二叉搜索树95 不同的二叉搜索树II (五)前缀树108 实现前缀树 (六)前缀和+hash560 和为k的子数组个数437 路径总和III


    前言

    二叉树的部分是面试中的超级重点,几乎是必问的。

    对二叉树的掌握主要是前序、中序、后序遍历思想的灵活掌握,最重要的是灵活的使用深度优先搜索思想和广度优先搜索思想,这部分由一点点需要思考,但是不要有害怕的这种想法,因为掌握的套路之后,再加上有题目的经验,我们总能够想到方法的。

    下面依然是对二叉树的题目进行了分类整理。题目来源是leetcode100题和剑指offer,记住,质比量重要,谨慎对待每一题,透彻理解里面的思路和思想。

    自己写的时候,不要看别人的代码,整理好思路后,自己在IDE里写代码,然后在比较不同。 一共24题,其实并不多,哈哈,加油,加油……


    (一) 遍历及相关题目(前中序)其实本质还是深搜DFS,看你怎么想了。

    94 二叉树的中序遍历

    中序遍历:左子树->跟->右子树 第一种方法就是递归,这种方法无需多言,数据结构都学过;

    第二种方法就是用栈,大家都知道递归是可以用栈模拟的,这里学会用栈的方法,有助于我们了解到中序遍历的过程。 用到的数据结构——Deque双端队列 既可以操作队列头,也可以操作队列尾,我们可以用这个数据结构实现栈、队列,非常非常好用哦……

    我一般解题的时候都用不抛出异常的方法,这一题是要作为栈使用,就用入栈:offerLast(e)、出栈:pollLast()、栈顶:peekLast();当然你要用first结尾的也一样可以。

    思路---------------------- (1)根据中序遍历的定义,需要先递归左节点,再递归左节点的左节点,这样一直让左节点入栈

    while(root!=null){ stack.offerLast(root); root = root.left; }

    (2)每当上面代码退出的时候,也就是递归左节点到了尽头,接着就可以从当栈中弹出一个元素,加入结果序列后,让该节点的right 入栈。 当然前提值栈不空 stack.peekLast() != null

    root = stack.pollLast(); res.add(root.value); root = root.right;//到右子树去递归入栈去

    (3) 接着回到(1)循环,直到栈空为止。总之,我们要尽可能的向left走,万般无奈向右边走一步,在弹栈的时候添加到结果集。

    中序遍历代码-------------------------------------------------------------------------

    class solution{ /** *递归法----- */ void inorder(TreeNode root,List<Integer> res){ if(root==null) return; inorder(root.left,res); res.add(root.value); inorder(root.right,res); } /** *栈模拟 */ public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); Deque<TreeNode> stack = new LinkedList<TreeNode>(); while(stack.peekLast()!=null || root!=null){ while(root!=null){//一路向左 stack.offerLast(root); root = root.left; } root = stack.pollLast();//输出结果 res.add(root.val); root = root.right;//拐弯到右子树 } return res; } //补充——前序遍历的栈模拟法 public List<Integer> preorderTraversal(TreeNode root){ List<Integer> res = new ArrayList<Integer>(); Deque<TreeNode> stack = new LinkedList<TreeNode>(); while(stack.peekLast()!=null || root!=null){ while(root!=null){//一路向左 res.add(root.val);//递归的时候就输出结果 stack.offerLast(root); root = root.left; } root = stack.pollLast();//拐弯到右子树 root = root.right; } return res; } //补充——后序遍历的栈模拟法——右左跟 输出再倒序 public List<Integer> preorderTraversal(TreeNode root){ List<Integer> res = new ArrayList<Integer>(); Deque<TreeNode> stack = new LinkedList<TreeNode>(); while(stack.peekLast()!=null || root!=null){ while(root!=null){//一路向右 res.add(root.val);//递归的时候就输出结果 stack.offerLast(root); root = root.right; } root = stack.pollLast();//拐弯到左子树 root = root.left; } return Collections.reverse(res); } }

    105 前序遍历和中序遍历的结果构造出二叉树——分治法

    思路:递归分治构造二叉树,其中找中序中跟的位置是用的map优化 时间复杂度:O(n),n是节点的个数 空间复杂度栈深度h<map长度n,所以O(n)

    class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { Map<Integer,Integer> map = new HashMap<>(); for(int i=0;i<inorder.length;i++) map.put(inorder[i],i); return recur(preorder,0,preorder.length-1,inorder,0,inorder.length-1,map); } public TreeNode recur(int[] preorder,int preleft,int preright,int[] inorder,int inleft,int inright,Map<Integer,Integer> map){ if(preleft>preright || inleft>inright) return null; TreeNode root = new TreeNode(preorder[preleft]); int pIndex = map.get(preorder[preleft]); root.left = recur(preorder,preleft+1,pIndex-inleft+preleft,inorder,inleft,pIndex-1,map); root.right = recur(preorder,pIndex-inleft+preleft+1,preright,inorder,pIndex+1,inright,map); return root; } }

    538 把二叉搜索树转化为累加树(变形遍历题)

    要求将每个节点的值更新为比她大的节点值之和 采用右子树-根节点-左子树的遍历方式,设置一个全局变量,每次记录过程中累加的值。我们可以想象一下前序遍历的结果是一个递增序列,所以这种递归的结果就是一个递减的序列,每次让前面的累加,就得到结果了。 我们之后有一题——判断二叉搜索树,其实那一题可以用类似的做法,就是前序遍历,每次判断是不是比前一个数大。

    /** *首先是用栈模拟,非递归法 */ class Solution { int sum = 0; public TreeNode convertBST(TreeNode node) { TreeNode root = node; if(root == null) return null; LinkedList<TreeNode> stack = new LinkedList(); while(!stack.isEmpty() || root!=null){ while(root!=null){ stack.push(root); root = root.right; } TreeNode cur = stack.pop(); sum+=cur.val; cur.val = sum; root = cur.left; } return node; } } /** *下面是递归的方式 */ class Solution { int sum = 0; public TreeNode convertBST(TreeNode root) { if(root==null) return null; convertBST(root.right); sum+=root.val; root.val = sum; convertBST(root.left); return root; } }

    剑指offer54 求BST倒数第k大节点

    太简单了,但是要学会将k设置为全局变量,否则,作为参数是不可以每次右-中-左,在中-1,应该设一个全局变量。

    class Solution { int a = 0,k; public int kthLargest(TreeNode root, int k) { this.k = k; help(root); return a; } private void help(TreeNode root){ if(root==null) return ; help(root.right); k--; if(k==0){ a = root.val; return; } help(root.left); } }

    (二)BFS和DFS

    102二叉树的层序遍历——BFS

    class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>();//new 集合的写法 Queue<TreeNode> queue = new LinkedList<TreeNode>(); if(root==null) return res; queue.offer(root); while(!queue.isEmpty()){ ArrayList<Integer> curlevel = new ArrayList<Integer>(); int size = queue.size(); for(int i = 0;i<size;i++){//一次输出一层,一层大小是queue.size() TreeNode node = queue.poll(); curlevel.add(node.val); if(node.left!=null) queue.offer(node.left); if(node.right!=null) queue.offer(node.right); } res.add(curlevel); } return res; } }

    104 二叉树的最大深度——BFS和DFS都可以

    法一 DFS递归 最大深度 = 1+max{左子树最大深度,右子树最大深度} 时间复杂度是O(n) 空间复杂度是O(height)这是栈的深度 也可以看做是后序遍历,先计算左右子树的深度,再算根节点的深度,返回的是根节点的深度

    class Solution { public int maxDepth(TreeNode root) { if(root==null) return 0; return 1 + Math.max(maxDepth(root.left) , maxDepth(root.right)); } }

    法二 BFS层序遍历一遍,每次处理完一层 ,level加一,最后level数量就是最大深度. 时间度复杂度O(n),空间复杂度O(n),队列存储元素个数。

    class Solution { public int maxDepth(TreeNode root) { if(root==null) return 0; int level = 0; Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); while(!queue.isEmpty()){ int size = queue.size(); for(int i = 0;i<size;i++){ TreeNode node = queue.poll(); if(node.left!=null) queue.offer(node.left); if(node.right!=null) queue.offer(node.right); } level++; } return level; } }

    617 合并二叉树——DFS

    其实就是遍历一遍,所以宽搜深搜都可以。 深搜,当有一个是空的时候,合并结果是非空的那颗树,否则两棵树都不空,则合并的结果为:当前节点改value,然后递归合并左子树右子树。 时间复杂度:0(min(t1个数,t2个数)) 空间复杂度是:O(min(t1深度,t2深度))

    class Solution { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if(t1==null) return t2; if(t2==null) return t1; t2.val = t1.val + t2.val; t2.left = mergeTrees(t1.left,t2.left); t2.right = mergeTrees(t1.right,t2.right); return t2; } }

    当然,我们也可以想象成自己是在用先序遍历,先处理根节点,然后处理左子树、右子树。

    226 翻转二叉树——DFS(前序可以 后序也可以)

    法一:后序遍历法 就是让左右子树先交换,也就是一直押栈,最后左右子树都翻转成功了,再处理根节点。交换root.left和root.right

    TreeNode invertTree(root){ if (root == null) // 遍历到null节点时,不用翻转,直接返回它本身 return root; invertTree(root.left); // 递归压栈压到底 invertTree(root.right); TreeNode temp = root.left; // 执行交换 root.left = root.right; root.right = temp; return root; }

    思路二:先序遍历法 就是根节点先交换左右子树,然后再处理左右子树

    TreeNode invertTree(root){ if (root == null) { // 遍历到null节点时,不用翻转,直接返回它本身 return root; } TreeNode temp = root.left; root.left = root.right; root.right = temp; invertTree(root.left); invertTree(root.right); return root; }

    101 对称二叉树——DFS和BFS

    解法一: 两棵树镜面对称 = 根相等 && 1树的左子树和2树右子树镜面对称 && 1树右子树和2树左子树镜面对称。 时间复杂度O(n) 空间复杂度O(深度)

    class Solution { public boolean isSymmetric(TreeNode root) { return ismirror(root,root); } public boolean ismirror(TreeNode t1,TreeNode t2){ if(t1==null&&t2==null) return true; if(t1==null || t2==null) return false; return t1.val==t2.val && ismirror(t1.left,t2.right) && ismirror(t1.right,t2.left); } }

    解法二: 开始连续让两棵树的根节点如队列, 然后每次从队列里poll出两个元素,看看是不是相等 再分别让1元素的左和2元素的右进去,1元素的右和2元素的左进去,当然再两者是镜面的情况下。 时间复杂度O(n) 空间复杂度O(n),节点进队出队,不会超过n

    class Solution { public boolean isSymmetric(TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); queue.offer(root); while(!queue.isEmpty()){ TreeNode t1 = queue.poll(); TreeNode t2 = queue.poll(); if(t1==null&&t2==null) continue; if(t1==null||t2==null) return false; if(t1.val!=t2.val) return false; queue.offer(t1.left); queue.offer(t2.right); queue.offer(t1.right); queue.offer(t2.left); } return true; } }

    236 二叉树的最近公共祖先——DFS(后序)

    当前树根节点叫做root,求p和q的最近公共祖先,使用后序遍历的顺序,从最左叶子和它的兄弟开始找p和q,一点一点向他们的爸爸爷爷上蔓延。发现p或者q了,返回,表示找到了。

    root = p,返回p——递归终止root = q,返回q——递归终止root不是p也不是q,递归找p、q分别在root的left子树和right子树,此时最近祖先就是root,返回——递归终止 class Solution{ public TreeNode lowestCommonAncestor(TreeNode root,TreeNode p,TreeNode q){ if(root==null || root==p || root==q)//从根节点到叶子的找到的是第一个q或者p的那个节点 return root; //去左右子树中找p、q的最近祖先 TreeNode left = lowestCommonAncestor(root.left,p,q); TreeNode right = lowestCommonAncestor(root.right,p,q); if(left==null) return right;//左子树没有p也没有q if(right==null) return left;//右子树没有p也没有q return root; } }

    98 验证二叉搜索树——DFS(中序遍历或者定义)

    解法一:根据递归定义,二叉搜索树的左子树都小于根节点,右子树都大于根节点。每个节点都有一个上下界。

    class Solution{ public boolean isValidBST(TreeNode node){ return withinBounds(node,Long.MIN_VALUE,Long.MAX_VALUE); } public boolean withinBounds(TreeNode root,long low,long high){ if(root==null) return true; if(root.val<=low || root.val>=high) return false; //左右子树的上下界是否满足条件,小心左子树更新上界为当前节点,右子树更新下界为当前节点。 return withinBounds(root.left,low,root.val) && withinBounds(root.right,root.val,high); } }

    解法二:二叉搜索树的中序遍历应该是一个递增的序列。 二叉搜索树的的中序遍历的结果应该是一个递增的序列。所以我们可以后序遍历一个二叉搜索树,设置一个全局变量存储上次的那个值。

    class Solution { Long lastVal = Long.MIN_VALUE;//设成Long的最小值,防止节点的值=Integer.MIN_VALUE public boolean isValidBST(TreeNode root) { if(root==null) return true; if(!isValidBST(root.left)) return false;//左子树 if(root.val<=lastVal) return false;//当前根节点 lastVal = (long)root.val; return isValidBST(root.right);//右子树 } }

    114 二叉树展开为链表(后序或者迭代)

    方法一: 后序遍历,先让右子树展开,再展开左子树,然后展开跟

    class Solution { public void flatten(TreeNode root) { if(root==null) return; flatten(root.right); flatten(root.left); TreeNode cur = root.left; if(cur!=null){ while(cur.right!=null) cur = cur.right; cur.right = root.right; root.right = root.left; root.left=null; } } }

    方法二:迭代,从根节点开始,每次展开当前最右边的那个节点的左子树

    297 二叉树的序列化与反序列化(BFS序列化BFS反序列化)

    public class Codec { public String serialize(TreeNode root){ if(root==null) return "[]"; StringBuilder res = new StringBuilder("["); Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while(!queue.isEmpty()){ TreeNode node = queue.poll(); if(node!=null){ res.append(node.val+","); queue.add(node.left); queue.add(node.right); } else res.append("null,"); } res.deleteCharAt(res.length()-1); res.append("]"); return res.toString(); } public TreeNode deserialize(String data){ if(data.equals("[]")) return null; String[] vals = data.substring(1,data.length()-1).split(","); TreeNode root = new TreeNode(Integer.parseInt(vals[0])); Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); int i=1; while(!queue.isEmpty()){ TreeNode node = queue.poll(); if(!vals[i].equals("null")){ node.left = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.left); }else{ node.left = null; } i++; if(!vals[i].equals("null")){ node.right = new TreeNode(Integer.parseInt(vals[i])); queue.add(node.right); }else{ node.right = null; } i++; } return root; } }

    (三)路径相关

    112 路径总和(BFS 递归都可以)

    这一题简单的很,回溯递归,每次判断跟节点到当前叶子节点的和是不是等于sum

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

    113 路径总和II(DFS递归)

    和112没啥区别,只要每次发现到达叶子节点且满足条件的时候,就将记录当前路径放入结果集。

    class Solution { public List<List<Integer>> pathSum(TreeNode root, int sum) { List<List<Integer>> paths = new ArrayList<>(); if(root==null) return paths; dfs(root,sum,paths,new ArrayList<>()); return paths; } private void dfs(TreeNode root,int sum,List<List<Integer>> paths,List<Integer> curPath){ if(root==null) return; sum-=root.val; if(sum==0 && root.left==null && root.right==null){ curPath.add(root.val); paths.add(new ArrayList(curPath)); curPath.remove(curPath.size()-1); } curPath.add(root.val); dfs(root.left,sum,paths,curPath); dfs(root.right,sum,paths,curPath); curPath.remove(curPath.size()-1); } }

    543 求二叉树最大直径——DFS

    发现短路径中的最大者一定是叶子节点之间,所以问题就转化成了——求叶子节点间最短路径中的最大值 这一题不是直接求的,必须转化为求根节点到孩子节点的深度那一题,中序遍历,先求左深度,再求右深度,这时候就可以更新最大距离了。 叶子节点间最短路径 = 公共父节点到两个叶子的高度之和。如左子树的深度是5,有4条边,右子树深度是4,有3条边,那么它们间的直径就是9,还有到叶子节点的那两条边。

    求深度可以用深搜的求节点深度的方法:深度 = max{左子树深度,右子树深度} + 1。这种方法求出的最大深度不是指边的深度,而是层数,比如说如果只有一个节点,这个节点的深度就是1,而不是0。

    设置一个全局变量max存储最大 最短路径 max = max{left深度 +right深度,max}

    class Solution { int maxlen = 0; public int diameterOfBinaryTree(TreeNode root) { depth(root); return maxlen; } public int depth(TreeNode root){ if(root==null) return 0; int left = depth(root.left); int right = depth(root.right); maxlen = Math.max(left+right,maxlen); return Math.max(left,right) + 1; } }

    124 二叉树两点间最大路径和

    这一题和上面一题 求最大直径类似,也是不能直接求,怎么办呢? 求以当前根节点到为起点的一条路径,使得该路径的节点值之和最大。这也是一个后序遍历过程,左子树为首的最大、右子树为首的最大、当前节点。更新max=MAX{max,root,root+left,root+right,root+left+right} 简化的思路——help(root)返回值是以root为出发点的最大贡献值,要不是0,要不是正数。因为如果子树的贡献为负数,就不要子树就好,这样也好写代码。

    class Solution { int maxSum = Integer.MIN_VALUE; public int maxPathSum(TreeNode root) { help(root); return maxSum; } private int help(TreeNode root){//计算root为根这条树的最大贡献 if(root==null) return 0; int left = Math.max(help(root.left),0);//计算左子树的最大 int right = Math.max(help(root.right),0);//右子树最大贡献值 //更新maxSum包含当前节点时的最大值 maxSum = Math.max(maxSum , root.val+left+right); return root.val + Math.max(left,right);//该子树的最大贡献值 } }

    687最长同值路径

    这一题和i前两题一样也是后序思路,用一个全局变量记录最长的长度,help(root)计算以root出发最长单向的同值路径长度(层数),我们的最长变量就在算完左右的时候更新。

    class Solution{ int maxlen = 0; public int longestUnivaluePath(TreeNode root){ help(root); return maxlen; } private int help(TreeNode root){ if(root==null) return 0; int left = help(root.left); int right = help(root.right); if(root.left!=null && root.left.val==root.val) left++; if(root.right!=null && root.right.val==root.val) right++; maxlen = Math.max(maxlen,left+right); return Math.max(left,right); } }

    (四)二叉树的动态规划

    337 打家劫舍III

    这一题就是使用的动态规划的思想,每次看看不放根节点和放根节点的时候,各自的最优值是多少财宝使用后序遍历的思想,因为当前节点的最优值需要起来孩子的结果。

    class Solution { public int rob(TreeNode root) { if(root==null) return 0; int[] a = dp(root); return Math.max(a[0],a[1]); } private int[] dp(TreeNode root){ if(root==null) return new int[]{0,0};//0代表不选,1是选 int[] left = dp(root.left); int[] right = dp(root.right); int[] good = new int[2]; good[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]); //选root good[1] = left[0]+right[0]+root.val;//不选root return good; } }

    96 不同的二叉搜索树

    计算1-n可以组成的BST个数,发现可以使用动态规划

    class Solution { public int numTrees(int n) { int[] dp = new int[n+1]; dp[0] = 1; for(int i=1;i<=n;i++){//以0-i可以构成的个数 for(int j=1;j<=i;j++){//0-i分别作为根节点 dp[i]+=dp[j-1]*dp[i-j]; } } return dp[n]; } }

    95 不同的二叉搜索树II

    我们已经知道这一题是存在大问题、小问题的情形的,所以总体上就用分治的思路,比如要计算【l,r】的所有二叉树,就把它分解为当l-r分别是根节点的时候,分别计算l——i-1和i+1——r的二叉树集合,两个集合排列组合作为当前跟的左右子树。就完成了本体的解答。

    class Solution { public List<TreeNode> generateTrees(int n) { if(n==0) return new ArrayList<>(); return dfs(1,n); } private List<TreeNode> dfs(int l,int r){//从l到r可以构成的二叉搜索树 List<TreeNode> res = new ArrayList<>(); if(l>r){ res.add(null); return res; } for(int i=l;i<=r;i++){ List<TreeNode> left = dfs( l,i-1); List<TreeNode> right = dfs(i+1,r); for(TreeNode leftNode:left) for(TreeNode rightNode:right){ TreeNode root = new TreeNode(i,leftNode,rightNode); res.add(root); } } return res; } }

    (五)前缀树

    108 实现前缀树

    这一题挺简单的,我们知道什么是前缀树,构件它就行了。根据题意可以是个最多26叉的数。

    (六)前缀和+hash

    560 和为k的子数组个数

    方法一: 定义一个前序和数组,存放前序和。从i-j暴力枚举pre[j]-pre[i-1],O(n^2)复杂度

    方法二: 定义一个当前前序和变量和一个哈希表,存放数组的前序和 以及该前序和出现的次数 一次遍历,每次先更新检查 当前前序和-sum 在hash中出现的次数,累加之,然后到更新这个hash,再次到下一个节点。

    437 路径总和III

    方法一: 从前两题的路径总和我们可以发现,我们的一般的解法只能计算出从根节点出发到下面的路径,是不是和==sum,如果题目是这样的,我们会发现简单很多,直接先序遍历一遍(深搜回溯),每次看看当前节点的当前路径和是不是sum就可以了,也就是我们的helper函数。 然而,题目不是让我们必须从跟姐点出发,所以要遍历一点这棵树,每次把当前节点作为跟节点,也就是出发点。 结果 = 根节点作为出发点个数 + 左子树做出发点 + 右子树做出发点

    时间复杂度:O(N*N) 空间复杂度:O(N)

    class Solution { public int pathSum(TreeNode root, int sum) { if(root==null) return 0; return helper(root,sum)+pathSum(root.left,sum)+pathSum(root.right,sum); } //从root出发遍历 //每次查看是否 [跟,当前节点]==sum //上一步个数 + 跟——左子树 + 跟——右子树 private int helper(TreeNode root,int sum){ if(root==null) return 0; //当前节点是结束点时个数 sum -= root.val; int res = 0; if(sum==0) res++; res+=helper(root.left,sum);//向左走接着下来多少个 res+=helper(root.right,sum);//右作为结束点 return res; } }

    方法二 前缀和的方式优化,用一个map保存从根节点到当前节点的各个前缀和,当然,因为回溯,这个map存的就是当前的单支路径上的前缀和。以及出现那些前缀和的个数。比如数组[1,2,3,0],前缀和map就是{0:1, 1:1, 3:1, 6:2} 我们一次遍历这棵树,如果 当前节点的前缀和-sum==target,且在map中target的出现的个数是3,就说明了以当前节点作为接结束,父辈节点作为开始的满足条件的路径有3条,累加到结果了即可。

    时间复杂度:O(N) 空间复杂度:O(h)

    class Solution { public int pathSum(TreeNode root, int sum) { if(root==null) return 0; HashMap<Integer,Integer> map = new HashMap<>(); map.put(0,1); return helper(root,sum,map,0); } //从root出发遍历,相当于先序遍历,每次查看是否当前节点到跟的路径==sum,是则+1 private int helper(TreeNode root,int sum,HashMap<Integer,Integer> map,int curSum){ if(root==null) return 0; int res = 0;//保存结果路径条数 curSum+=root.val;//更新当前前缀和 if(map.containsKey(curSum-sum))//当前节点可以和父亲们构成满足条件的路径 res+=map.get(curSum-sum); //当前节点放入前缀和map,为递归左右子树做准备 map.put(curSum,map.getOrDefault(curSum,0)+1); res+=helper(root.left,sum,map,curSum); res+=helper(root.right,sum,map,curSum); //不用remove了,没有就减一就好,分数==0不会影响 map.put(curSum,map.getOrDefault(curSum,1)-1); return res; } }
    Processed: 0.024, SQL: 8