我的刷题之旅 —— 二叉树

我的刷题之旅 —— 二叉树

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



前言

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

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

下面依然是对二叉树的题目进行了分类整理。题目来源是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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值