算法通关村——轻松搞定二叉树基础问题

算法通关村系列文章目录



前言

本系列文章是针对于鱼皮知识星球——编程导航中的算法通关村中的算法进行归纳和总结。 该篇文章讲解的是第八关中的青铜挑战———二叉树的经典算法问题

这一期主要讲的是一些二叉树比较常见,比较经典的算法问题,包括二叉树的双指针,路径,还有翻转等比较常见的一些问题。


一、二叉树里的双指针

在前面数组那部分,我们将了双指针的一些用法,那在二叉树中,同样也有一些对于双指针的用法,其实方法大致都是相同的,而且二叉树对应的两个子树,也比较适合于用双指针来写


1.1 相同的树

LeetCode 100:
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
在这里插入图片描述

输入:p = [1,2,3], q = [1,2,3]
输出:true

这里看两个二叉树是否是相同的二叉树,不仅要看结构是否一致,还要看各个节点的值是否一致。其实这就是把两个二叉树的遍历放到一起了。那这里二叉树的遍历方式,可以选择前序,中序,后序,层序等等都可以,同样也可以使用迭代的方式遍历,也可以使用递归的方式进行遍历

这里的双指针就是 first 和 second 两个变量,然后这里没用用到什么快慢,还有碰撞之类的,只是简单的两个变量对比,然后同时往前移动而已,还是比较简单的
前序遍历——迭代方式

    public static boolean isSameTree(TreeNode p, TreeNode q) {
        TreeNode first = p;
        TreeNode second = q;
        Deque<TreeNode> stack1 = new LinkedList<>();
        Deque<TreeNode> stack2 = new LinkedList<>();
        while ((!stack1.isEmpty() || first != null) || (!stack2.isEmpty() || second != null)) {
        // 先把左树遍历完,再去找有树,典型的前序遍历,只用在中间判断一下值是否一样,
        
            while (first != null && second != null) {
                if (first.val != second.val) return false;
                stack1.push(first);
                stack2.push(second);
                first = first.left;
                second = second.left;
            }
            if (first == null && second != null) return false;
            if (first != null && second == null) return false;
            first = stack1.pop();
            second = stack2.pop();
            first = first.right;
            second = second.right;
        }
        return true;
    }

前序遍历——递归法

    public static boolean isSameTreeDIGUI(TreeNode p, TreeNode q) {
        if(p==null&&q==null){
            return true;
        }
        if(p==null||q==null){
            return false;
        }
        if(p.val== q.val){
            return false;
        }
        return isSameTreeDIGUI(p.left,q.left) && isSameTreeDIGUI(p.right,q.right);
    }

1.2 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。
在这里插入图片描述

输入:root = [1,2,2,3,4,4,3]
输出:true

这个对称二叉树其实用递归的话是很好做的,是需要比较该节点的左右节点的值是否相同,然后就不管了,后面只看左节点的左树与右节点的右树是否相同,再看左节点的右树与右节点的左树是否相同,我们看图也能看出来这一层关系,其实递归只要找到公式,找到每一层要做的事,然后让每一层都去做就好了。后面到动态规划那里,也会把递归和动态规划关联起来,将知识穿起来,穿成一串。

public static boolean isSymmetric(TreeNode root) {
        if(root==null) return true;
        return recursionJudge(root.left,root.right);
    }


    public static boolean recursionJudge(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 recursionJudge(left.left,right.right) && recursionJudge(left.right,right.left);

    }

1.3 合并二叉树

给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

返回合并后的二叉树。

注意: 合并过程必须从两个树的根节点开始。

输入:root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7]
输出:[3,4,5,5,4,null,7]

在这里插入图片描述

这个合并二叉树就比之前的那两道题要有难度了,之前那两道题基本上只要看到图,再稍微理解一下基本就都出来了。这道题还是要稍微往深处去理解一下的。

步骤

  1. 前序递归遍历两颗树,并且递归函数的返回值🌲两颗树相同位置节点,合并后的节点
  2. 前序遍历两颗树,如果两颗树的相同位置节点都是null,就直接返回null好了
  3. 如果 t1为null,那我们就直接返回t2就好了
  4. 如果 t2为null,那我们就直接返回t1就好了
  5. 如果都不为null,就直接将两个值相加,同时再赋值给t1,然后这个节点就算合并完了,该考虑下一层的节点了,就递归一下,递归两次,第一次,合并这两个节点的左节点,第二次,合并这两个节点的右节点。最后将第一颗树作为结果输出。
public static TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if(t1==null&&t2==null) return null;
        if(t2==null){
            return t1;
        }else if(t1==null){
            return t2;
        }else {
            t1.val+= t2.val;
            t1.left=mergeTrees(t1.left,t2.left);
            t1.right=mergeTrees(t1.right,t2.right);
            return t1;
        }
    }

二、路径专题

这里我们先简单地通过几道题了解一下深度优先遍历的知识,为后面的回溯,动态规划做铺垫

2.1 二叉树的所有路径

LeetCode 257:

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
在这里插入图片描述

输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]

其实深度优先遍历就是将一条路走完以后,再回去,找分支。在下面的代码中也有所体现,当我们将该节点处理完毕后,是直接就开始往从子节点继续开始遍历了,直到遍历到叶子节点,才回去遍历其他的分支

    public static List<String> binaryTreePaths(TreeNode root) {

        List<String> result=new ArrayList<>();
        dfs(root,"",result);
        return result;
    }



    public static void dfs(TreeNode node,String path,List<String> result){
        if(node==null) return;
//        StringBuilder stringBuilder=new StringBuilder();
        if(node.right==null&&node.left==null){
            result.add(path+node.val);
            return;
        }
//        path.append(node.val).append("->");
        if(node.left==null){
            dfs(node.right,path+node.val+"->",result);
            return;
        }
        if(node.right==null){
            dfs(node.left,path+node.val+"->",result);
            return;
        }
//        StringBuilder stringBuilder = new StringBuilder(path.toString());
        dfs(node.left,path+node.val+"->",result);
        dfs(node.right,path+node.val+"->",result);
    }

总结

虽然LeetCode中还有一些题目比如226,112题都是比较基础的一些题目,这里就再给出详细的解答了,大家可以自己按着前面的思路一步一步把问题梳理明白了,代码只是最后一步了。本节的题目还是相对来说比较容易一些,下一期将为大家带来第八关的白银挑战,白银挑战的题目相对来说就有点难度了,不过大体思路跟本期还是相同的。那么本期青铜挑战的讲解我们就先到这里结束了。

我是Mayphyr,从一点点到亿点点,我们下次再见

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值