算法通关村系列文章目录
前言
本系列文章是针对于鱼皮知识星球——编程导航中的算法通关村中的算法进行归纳和总结。 该篇文章讲解的是第八关中的青铜挑战———二叉树的经典算法问题
这一期主要讲的是一些二叉树比较常见,比较经典的算法问题,包括二叉树的双指针,路径,还有翻转等比较常见的一些问题。
一、二叉树里的双指针
在前面数组那部分,我们将了双指针的一些用法,那在二叉树中,同样也有一些对于双指针的用法,其实方法大致都是相同的,而且二叉树对应的两个子树,也比较适合于用双指针来写
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]
这个合并二叉树就比之前的那两道题要有难度了,之前那两道题基本上只要看到图,再稍微理解一下基本就都出来了。这道题还是要稍微往深处去理解一下的。
步骤
- 前序递归遍历两颗树,并且递归函数的返回值🌲两颗树相同位置节点,合并后的节点
- 前序遍历两颗树,如果两颗树的相同位置节点都是null,就直接返回null好了
- 如果 t1为null,那我们就直接返回t2就好了
- 如果 t2为null,那我们就直接返回t1就好了
- 如果都不为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,从一点点到亿点点,我们下次再见