Leetcode-二叉树

目录

 DFS算法(深度优先算法)

 144. 二叉树的前序遍历

145. 二叉树的后序遍历

226. 翻转二叉树

101. 对称二叉树

572. 另一棵树的子树

104. 二叉树的最大深度

404. 左叶子之和

236. 二叉树的最近公共祖先

235. 二叉搜索树的最近公共祖先

 BFS(广度优先遍历)

112. 路径总和

102. 二叉树的层序遍历

199. 二叉树的右视图


好久没写博客了,主要是没时间,跟着学校工作室的安排,这周时间在Leetcode上刷了一些关于二叉树的题目,其中设计大量的递归和一系列让我头痛的算法,我想有必要进行一些总结,防止再次遇到依然不会做(虽然最终可能还是不会做),在这次总结中,可能会参照自己写的代码和答案中的代码,进行一些思路的提取,和类型题的做题技巧.这篇博客也是由浅到深,相信大家都看的懂

5b4a68986e514ee3a0a33aabcbee1591.png

 DFS算法(深度优先算法)

 144. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 

输入:root = [1,null,2,3]

输出:[1,2,3] 

这道题属于二叉树的入门,相信很多人在自己的编译器里实现了很多遍,但是在leetcode的oj上可能还要考虑一些接收数据的情况,比如上述代码就使用单列集合来接收数据.

这里也使用了递归算法,但是这里的递归比较简单,进行最基本的深度优先遍历即可,但是为了更好的递归,所以这里写了两个函数,在后面的题目中可能会存在大量需要使用两个函数的情况,所以我在这里也使用了两个函数,通过每次递归遍历将数值add到集合中,由于集合属于对地址的引用,所以可以通过在其他函数中可以更新集合中的值.

下面也给出在一个函数中实现前序遍历的函数,但是当函数越复杂,在一个函数中实现可能会变得更复杂,所以推荐通过增加函数的个数,把复杂的题进行拆解

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        //创建一个集合,用来接收遍历的值
        List<Integer> ret = new ArrayList<>();
        dfs(ret,root);
        return ret;
    }
    public static void dfs(List<Integer> ret,TreeNode root){
        if(root == null)return;
        ret.add(root.val);
        dfs(ret,root.left);
        dfs(ret,root.right);
    }
}

 至于怎么递归,这里一定要选择画图,无论是手写还是用电脑,建议把递归的流程模拟实现一遍

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        ret.add(root.val);
        List<Integer> leftList = preorderTraversal(root.left);
        ret.addAll(leftList);
        List<Integer> rightList = preorderTraversal(root.right);
        ret.addAll(rightList);

        return ret;
    }
}

145. 二叉树的后序遍历

由于中序遍历,后序遍历跟前序遍历的思路一样,这里就直接给代码了,可以结合理解一下

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        postorder(root, res);
        return res;
    }

    public void postorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        postorder(root.left, res);
        postorder(root.right, res);
        res.add(root.val);
    }
}

94. 二叉树的中序遍历

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        inorder(root, res);
        return res;
    }

    public void inorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        inorder(root.left, res);
        res.add(root.val);
        inorder(root.right, res);
    }
}

226. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

e243978b005847ba8f36011f7f30fdbf.png

 翻转二叉树是指任意根节点的左右根节点进行交换val值, 刚开始做这道题的时候,一直在想,怎么实现三层及三层以上的左右结点的交换,实际上,拿第三层结点来举例,要想实现遍历其实只需要在第二层的时候左右结点进行交换,然后再对第三层结点进行交换即可.

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null){
            return null;
        }
        //从结点的角度出发,不从val的角度
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        invertTree(root.left);
        invertTree(root.right);

        return root;
    }
}

101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

c514376839be4b8ebbf90b5866d575ea.png

 要想判断二叉树是否对称,就需要对两边进行分析,判断两边是否相等,所以通过递归调用判断左节点和右结点是否相等

关于递归,最重要的就是要判断结束条件和控制返回值,这两个是递归代码的关键操作

这道题递归终止条件是两个结点都为空,或者两个结点有一个为空,或者两个结点的值不一样

通过这些来判断递归是否达到"递"的终点.再递归比较左节点的左孩子和右结点的右孩子,还有左节点的右孩子和右结点的左孩子.只有这两者都成立,最终判断的结果才是对称的二叉树,有任意条件不满足,比如某个对称点的值不相等,就返回false.

class Solution {
	public boolean isSymmetric(TreeNode root) {
		if(root==null) {
			return true;
		}
		//调用递归函数,比较左节点,右节点
		return dfs(root.left,root.right);
	}
	
	boolean dfs(TreeNode left, TreeNode right) {
		//递归的终止条件是两个节点都为空
		//或者两个节点中有一个为空
		//或者两个节点的值不相等
		if(left==null && right==null) {
			return true;
		}
		if(left==null || right==null) {
			return false;
		}
		if(left.val!=right.val) {
			return false;
		}
		//再递归的比较 左节点的左孩子 和 右节点的右孩子
		//以及比较  左节点的右孩子 和 右节点的左孩子
		return dfs(left.left,right.right) && dfs(left.right,right.left);
	}
}

572. 另一棵树的子树

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

929c7c7ce271438296514522cb50a302.png

 如果你看了上面对称二叉树这道题,你可能会察觉到这道题跟上面那到题的解题思路上有相似之处,都可以通过判断二叉树root的左节点和右结点是否与subRoot相等,就可以判断前者是否存在后者的子树.与上面代码一样,同样是分两个函数.递归函数的终止条件是

1.遍历到的结点处,其中的val值不相等,返回false

2.或者到达了叶子结点,返回true

3.存在一边为空,一边不为空的情况,此时返回false

class Solution {
    public static boolean isSameTree(TreeNode p, TreeNode q) {
        if((p != null && q == null) ||(p == null && q != null))return false;
        if(p == null && q == null)return true;
        if(p.val != q.val)return false;
            return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null) {
            return false;
        }
        //判断subroot与root相同的两棵树
        if(isSameTree(root, subRoot)) return true;
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
    }
}

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

ea658e6ca49047e9bc19ea78bacf7414.png

 这里的递归思想就是递归找到底部最深的元素,这里是进行比较关系,就需要在跟的左右结点的位置上找到最深的子节点

找出返回值:节点为空时说明高度为 0,所以返回 0;节点不为空时则分别求左右子树的高度的最大值,同时加1表示当前节点的高度,返回该数值

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

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

输入:root = [3,9,20,null,null,15,7]  

输出:2

f9883f1ae1804e40aa7085d905b27efd.png

找最小深度,如果你刚刚做完这个最大深度,你可能会出现跟我一样的错误,很多人写出的代码都不符合 1,2 这个测试用例,是因为没搞清楚题意题目中说明:叶子节点是指没有子节点的节点,这句话的意思是 1 不是叶子节点,题目问的是到叶子节点的最短距离

递归的终止条件:叶子节点的定义是左孩子和右孩子都为 null 时叫做叶子节点,当 root 节点左右孩子都为空时,返回 1,当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值.

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        //这道题递归条件里分为三种情况
        //1.左孩子和有孩子都为空的情况,说明到达了叶子节点,直接返回1即可
        if(root.left == null && root.right == null) return 1;
        //2.如果左孩子和由孩子其中一个为空,那么需要返回比较大的那个孩子的深度        
        int m1 = minDepth(root.left);
        int m2 = minDepth(root.right);
        //这里其中一个节点为空,说明m1和m2有一个必然为0,所以可以返回m1 + m2 + 1;
        if(root.left == null || root.right == null) return m1 + m2 + 1;
        
        //3.最后一种情况,也就是左右孩子都不为空,返回最小深度+1即可
        return Math.min(m1,m2) + 1; 
    }
}

404. 左叶子之和

给定二叉树的根节点 root ,返回所有左叶子之和。

f00dc3f529d844e784650694d4cee41f.png

拿到这道题,首先没看到这个叶子结点,导致我第一的想法是使用广度优先遍历(BFS)来求和,但是最终的答案总是错的,直至我找答案才发现我的问题出现在哪,答案介绍的是深度优先遍历,跟普通的递归算法一样找到终止条件,此题的终止条件:

既然是找叶子节点,肯定要判断当下结点的的左右结点是否为空结点.但是问题是可以遍历到叶子结点前一个结点进行判断吗,不行的,我首先想到的也是遍历前一个结点,判断当前结点不为空,左右结点也不为空的情况,但事实上,这样做通过上面的例子,最终会把7也加上去,原因是同样也满足我出的条件.所以这时候我们可以通过叶子结点的上上个结点进行判断具体代码如下:

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if(root == null)return 0;int ret = 0;//每次加上的值
        if(root.left != null && root.left.left == null && root.left.right == null){
            ret = root.left.val;
        }
        return ret + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
    }
}

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

ac0e124afb4f40b4a1470d3891ca1d21.png

做这道题之前,我也遇到过类似的题目,同样是寻求公共节点,但是前者做的时候,结点的值是按顺序递增的,也就是说,头节点是1,向下依次递增.当时求解的办法就是通过数学关系,父类是子列结点数值的一半,通过这个关系,可以把深度较大的结点,找到其父类结点,以此循环,最终相遇的结点就是其公共的祖先,但是这道题显然比上面讲的难度要高一点.

这道题要通过深度优先遍历找到底部的结点,通过返回值的root结点是否等于p或者q结点,如果包含其中的一个结点就返回true,此时要想找到最近祖先,此时要定义一个全局变量,这个变量就是最近祖先,满足祖先的条件是某个结点的左节点或右结点同时包含pq两个结点,还有一种情况是,这个结点本身就是p或者q,此时只要确保它的子节点包含一个结点即可.

class Solution {
    private TreeNode cur;
    private boolean dfs(TreeNode root,TreeNode p,TreeNode q){
        if(root == null)return false;
        boolean lson = dfs(root.left,p,q);
        boolean rson = dfs(root.right,p,q);  
        if((lson&&rson) || (p.val == root.val || q.val == root.val) && (lson || rson))
        cur = root;
        return lson || rson || (root.val == p.val || root.val == q.val);
    }
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root,p,q);
        return cur;
    }
}

235. 二叉搜索树的最近公共祖先

此题跟找普通的二叉树最近公共祖先一样,只是在此基础上多了一个条件,此二叉树是二叉搜索树.这就意味着它具有二叉搜索树的性质,左子树的val值小于次结点的val值,右子树的val值大于此结点的val值.所以此时就可以根据搜索树的性质,来找到公共结点,如果某个结点同时满足p中的val值小于此结点的val值,q结点中的val值大于此节点的val值,或者q中的val值小于此结点的val值,p结点中的val值大于此节点的val值就跳出循环,第一次满足这个条件的,此时的结点就是最近公共结点.如果不满足这两种情况,只需要遍历左右结点即可.

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //放在一个循环中,遍历整个搜索树
        TreeNode ancestor = root;
        while(true){
            if(p.val > ancestor.val && q.val > ancestor.val){
                ancestor = ancestor.right;
            }else if(p.val < ancestor.val && q.val < ancestor.val){
                ancestor = ancestor.left;
            }else{
                break;
            }
        }
        return ancestor;
    }
}

 BFS(广度优先遍历)

112. 路径总和

问题:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

e20d5d30f9e347f9ae2e9c1a4c57a5c8.png

 求结点数值域之和,我第一时间想到的也是使用BFS算法,毕竟广度优先算法虽然代码量比一般DFS算法要长,但是仔细多敲几遍,相信每一个人都能很好的掌握,这里通过这道题介绍一下BFS算法的一般求解步骤,说到这个算法,一般都需要与队列相结合,通过队列储存每一层所保存的结点,当遍历完之后,再通过poll释放,有时候要记录弹出的这个值,就需要相应的集合去接收即可,这道题就是这样,通过另一个队列取记录这个值,如果遍历的这个结点为下一个结点为空,就可以加上此节点的值与target值进行比较.如果相等,就返回true.

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        //BFS算法
        if(root == null)return false;
        Queue<TreeNode> queue1 = new ArrayDeque<>();
        if(root != null) queue1.offer(root);
        //收集前面层数之和
        Queue<Integer> queue2 = new ArrayDeque<>();
        queue2.offer(0);
        while(!queue1.isEmpty()){
            int n = queue1.size();
            for(int i = 0; i < n; i++){
                TreeNode cur = queue1.poll();
                int sum = queue2.poll();
                if(cur.left == null && cur.right == null && (cur.val + sum) == targetSum)
                return true;
                if(cur.left != null){
                    queue1.offer(cur.left);
                    queue2.offer(cur.val + sum);
                }
                if(cur.right != null){
                    queue1.offer(cur.right);
                    queue2.offer(cur.val + sum);
                }
            }
        }
        return false;
    }
}

 这里也给出答案中的算法,答案是使用递归的方式求解的.

观察要求我们完成的函数,我们可以归纳出它的功能:询问是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum

假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。

不难发现这满足递归的性质,若当前节点就是叶子节点,那么我们直接判断 sum 是否等于 val 即可(因为路径和已经确定,就是当前节点的值,我们只需要判断该路径和是否满足条件)。若当前节点不是叶子节点,我们只需要递归地询问它的子节点是否能满足条件即可。

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

102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 

这道题就是最典型的BFS算法,如果弄懂了上面拿到路径求和这道题,这道题就变得非常简单了.这里使用了二重集合,就是一个集合里面的元素同样也是集合,这个可以通过二维数组来理解,二维数组就是数组里的元素是数组.

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ret = new ArrayList<>();

        Queue<TreeNode> queue = new ArrayDeque<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()){
            int n = queue.size();
            List<Integer> list = new ArrayList<>();
            for(int i = 0; i < n; i++){
                TreeNode cur = queue.poll();
                list.add(cur.val);
                if(cur.left != null){
                    queue.add(cur.left);
                }
                if(cur.right != null){
                    queue.add(cur.right);
                }       
            }

            ret.add(list);   
        }
        return ret;
    }
}

199. 二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

输入: [1,2,3,null,5,null,4]

输出: [1,3,4]

3323107bfb264409981962c655eaaf44.png

 方法类似,可以自己敲几遍,与上面两道题的解题思路完全一样

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        //层序遍历树找到又试图
        List<Integer> list = new ArrayList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();

        if(root != null)queue.add(root);        
        while(!queue.isEmpty()){
            int n = queue.size();//测量此时队列结点个数
            for(int i = 0; i < n; i++){
                TreeNode cur = queue.poll();
                if(i == n - 1)
                list.add(cur.val);
                if(cur.left != null){
                    queue.add(cur.left);
                }
                if(cur.right != null){
                    queue.add(cur.right);
                }
            }
        }
        return list;
    }
}

由于本人水平有限,欢迎直接提出来😀

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值