前言❤️❤️
hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
这篇博客就到了数据结构中据说最难的部分——二叉树算法题😊🐵
二叉树结构的基础知识不算难,难的是算法题目,这部分之所以难,是因为基本上这些算法都使用了递归,虽然代码就那么几行,但是想理解透递归的调用过程,还是需要花费一些力气的
博主计划一共解析14道经典的二叉树算法题目,这篇博客解析7道,下篇博客解析7道,为了方便解析,这篇博客的所有题目博主都画了递归解析图,初学的铁汁强烈建议每道题目都自己画一下递归图,细品下过程
这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪
🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~
1,相同的树
题意就是传入两棵树的根结点,判断这两棵树是否完全相等,这个题目仍然可以使用递归的方法来解决,可以分为以下几步:
- 判定两个树的根结点是否相同
- 递归判定左子树是否相同
- 递归判定右子树是否相同
只要发现上面任何一个环节出现不同,就返回false,如果从头到尾都没有返回false,就返回true
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null){
return true;
}
if((p == null && q != null) || (p != null && q == null)){
return false;
}
if(p.val != q.val){
return false;
}
boolean isLeftSame = isSameTree(p.left,q.left);
boolean isRightSame = isSameTree(p.right,q.right);
return isLeftSame && isRightSame;
}
}
画图分析分析下递归过程

2,另一棵树的子树

这个题目让我们判断subRoot是否是root的子树(subRoot和root是两棵树的根结点),在上个题目的基础上,做这道题就简单
- 首先判断root和subRoot是不是相同的树(题目中给出了,如果两个树相同,subRoot也可以看作是root的子树)
- 递归判定root.left是否包含subRoot
- 递归判定root.right是否包含subRoot
只要中间有一处判定成功,就返回true
class Solution {
public static boolean isSameTree(TreeNode p,TreeNode q){
if(p == null && q == null){
return true;
}
if(p == null && q != null){
return false;
}
if(p != null && q == null){
return false;
}
if(p.val != q.val){
return false;
}
boolean isLeftSame = isSameTree(p.left,q.left);
boolean isRightSame = isSameTree(p.right,q.right);
return isLeftSame && isRightSame;
}
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null && subRoot == null){
return true;
}
if(root != null && subRoot == null){
return false;
}
if(root == null && subRoot != null){
return false;
}
if(isSameTree(root,subRoot)){
return true;
}
boolean isSubleft = isSubtree(root.left,subRoot);
boolean isSubRight = isSubtree(root.right,subRoot);
return isSubleft || isSubRight;
}
}
题目中已经明确指出了subRoot不为null了,这里博主的代码针对的是不加限制的情况,铁汁们写的时候可以把isSubtree方法刚开始的if判断少写一些
这个题目如果要画图分析递归,就只画isSubTree方法的递归图即可,把isSameTree就当成一个普通方法即可,如果同时画isSameTree和isSubTree的递归图,那就会超级复杂😅,递归过程分析如下:

3,翻转二叉树
这个题目依旧是使用递归,就是对根结点的左子结点和右子结点进行翻转,接着再进行递归,对根结点的左子结点的左子结点和右子结点进行翻转,以及对根结点的右子结点的左子结点和右子结点进行翻转,以此类推,就能把整个链表翻转过来
代码就是遍历二叉树,访问结点时,操作为交换该结点的左子结点和右子结点
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return null;
}
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
这个题目使用前序遍历和后序遍历都可以(博主比较推荐使用前序遍历的写法,因为比较好理解),但不能使用中序遍历,因为中序遍历是先针对左子树来进行递归,递归完成左子树后,那左子树就变成右子树,再去针对右子树进行递归,相当于递归的还是之前的左子树,也就是左子树递归了两次
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return null;
}
invertTree(root.left);
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
invertTree(root.right);
return root;
}
}
我们画图来看下中序遍历是如何递归的,可以看到是递归不到右子树上的,运行到invertTree(root.right),递归的还是原来左子树的结点

4,平衡二叉树
平衡二叉树:针对这个树的任何一个结点,左右子树的高度差不超过1,就认为是平衡二叉树,如下图

在二叉树(二)博客中,解析了计算二叉树的高度的方法,代码如下:
public int getHeight(TreeNode root){
if(root == null){
return 0;
}
return 1 + Math.max(getHeight(root.left),getHeight(root.right));
}
使用getHeight方法,先判断当前结点的左右子树的高度差有没有超过1,再递归的判断左子结点和右子结点的左右子树的高度差有没有超过1,就这样一直递归判断,直到判断到最后一层,递归是有一个结点不符合要求,这棵树就不是平衡二叉树
空结点或者单独一个结点,都算是平衡二叉树,就直接返回true
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
if(root.left == null && root.right == null){
return true;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
if(leftHeight - rightHeight > 1 || rightHeight - leftHeight > 1){
return false;
}
boolean isLeftBalanced = isBalanced(root.left);
boolean isRightBalanced = isBalanced(root.right);
return isLeftBalanced && isRightBalanced;
}
public int getHeight(TreeNode root){
if(root == null){
return 0;
}
return 1 + Math.max(getHeight(root.left),getHeight(root.right));
}
}
递归分析图如下(如果isBalanced里面传入的结点没有左右子树或者为null,博主就不再继续往下画了,直接返回true)

5,对称二叉树
判断对称二叉树就是判断根结点的左子树和右子树是否为镜像,如下图,左子树的根结点为t1,右子树的根结点为t2,判断镜像的方法分为以下几步:
- 判断根结点的值是否相同,如果不相同就直接返回false
- 递归判断t1.left和t2.right是否互为镜像
- 递归判断t1.right和t2.left是否互为镜像

我们写个isMirror方法,传入参数是两个子树的根结点,递归的来执行这些过程,如果在递归过程中一直都没有返回false,那isMirror方法最后就返回true
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirror(root.left,root.right);
}
public boolean isMirror(TreeNode t1,TreeNode t2){
if(t1 == null && t2 == null){
return true;
}
if((t1 != null && t2 == null) || (t2 != null && t1 == null)){
return false;
}
if(t1.val != t2.val){
return false;
}
boolean isMirror1 = isMirror(t1.left,t2.right);
boolean isMirror2 = isMirror(t1.right,t2.left);
return isMirror1 && isMirror2;
}
}
6,二叉树遍历

刷题平台上的算法题,分为两种类型,核心代码模式和ACM模式,大部分题目都是核心代码模式
前面的5道题目,就都属于是核心代码模式,不需要自己创建Node类,不需要考虑树是怎么创建起来的,有的也不需要考虑输出,就需要写个方法,题目中传入参数,再返回一个参数即可,比较简单
这个题目就是ACM模式,需要自己写结点类,处理输入,创建树,在main方法中处理输入和输出,要考虑的东西就比核心代码模式要多

题目中说空格字符代表空树,说先用“#”代表空格,但是看示例,输入的时候还是输入的"#“,描述的就不是很清楚,到底是输入的时候输入的是”#“,还是输入的实际是空格,为了方便我们看,用”#"表示,这个就有点歧义
这个问题很好解决,写代码的时候让输入空格可以,输入"#"也可以🐵
这个题目的重点就是如何通过先序遍历的序列来构建出二叉树(正常最少需要两种遍历的序列才能构建出二叉树,但是这个先序遍历序列中对于空树用空格表示出来了,属于特殊的先序序列)
就拿示例1输入的数据来举例,a一定为根结点,后面输入的是b,铁汁们细品下,有办法判断出b是a的左子结点,还是a的右子结点,如果是普通的前序序列,这个是没法判断的
但是,这个前序序列用空格字符代表空树了,a和b输入之间是没有空格的,因此b一定是a的左子结点(如果是右子结点的话,输入就是a#b)

到c这里,同理,c不可能是b的右子结点(输入为b#c时是这样),更不可能是a的右子结点(输入为b##c时是这样),c一定是b的左子结点

再接下来是##d,c的左右子树就是#,b的右子结点就是d

规律就是当一个结点的左子树都构造完成,再去构造这个结点的右子树,按照这个规律,就能构造出完整的树

因为是ACM模式,所以写代码时所有东西都要自己写:需要定义个Node类,还要考虑输入和输出,题目中提到会有多组输入数据,每组数据是一行字符串,读取输入时就是用while(scanner.hasNextLine),输出时也有要求,每个字符后面有一个空格,每个输出占一行
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static class Node{
public char val;
Node left = null;
Node right = null;
public Node(char val){
this.val = val;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 注意 while 处理多个 case
String line = in.nextLine();
Node root = build(line);
inOrder(root);
System.out.println();
}
}
public static Node build(String s){
}
public static void inOrder(Node root){
if(root == null){
return;
}
inOrder(root.left);
System.out.printf(root.val + " ");
inOrder(root.right);
}
}
核心就是这个build方法,里面传入字符串s,应该通过charAt(index)来读取字符的,就需要创建index成员变量,记录读取到哪个位置了,在main方法的while循环中,每次读取数据后,都需要把index清0
在bulid方法中,先用root.left = build(s),递归的构建左子树,再用root.right = build(s),递归的构建右子树
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 注意 while 处理多个 case
String line = in.nextLine();
Node root = build(line);
inOrder(root);
System.out.println();
}
}
public static int index = 0;
public static Node build(String s){
char c = s.charAt(index);
if(c == '#' || c == ' '){
return null;
}
Node root = new Node(c);
index++;
root.left = build(s);
index++;
root.right = build(s);
return root;
}
接着画图细品下递归过程

7,二叉树的层序遍历
层序遍历在二叉树(一)博客中就已经解析过,就是让元素入队列,出队列时进行打印,再把出队列的结点的left和right也加入到队列中,代码如下
public static void levelOrder(Node root){
if(root == null){
return;
}
Queue<Node> queue = new ArrayDeque<>();
queue.offer(root);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.printf(cur.val + " ");
if(cur.left != null){
queue.offer(cur.left);
}
if(cur.right != null){
queue.offer(cur.right);
}
}
}
但这个题目不是简单的层序遍历,输出时同一层中,同一层的结点输出时要放到同一个[]中
因为方法的返回类型是一个二维的List,不是打印按照层序来打印,如果还是用普通层序遍历,在元素出队列时,是很难判断这个元素到底加到哪个一维List中的
可以使用先序遍历,创建一个level变量,表示把元素加到哪个一维List中去,每次递归时level + 1
写个levelOrderHelper方法,里面传入三个参数:root、level、result,进行前序遍历,先把root.val添加到ressult中对应的一维List中,如果这个一维List还没有创建,就需要先创建下
接着再递归的处理root.left和root.right,递归调用levelOrderHelper方法时,需要level + 1(level的起始值为0,result.get(0)相当于是树的第一层的元素)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(result == null){
return result;
}
levelOrderHelper(root,0,result);
return result;
}
public static void levelOrderHelper(TreeNode root,int level,List<List<Integer>> result){
if(root == null){
return;
}
if(result.size() == level){
result.add(new ArrayList<>());
}
List<Integer> curRow = result.get(level);
curRow.add(root.val);
levelOrderHelper(root.left,level + 1,result);
levelOrderHelper(root.right,level + 1,result);
}
}
用示例1中的树,画图来模拟下递归过程

结语
- 这7道题目解决起来并不简单,有的直接可以在原方法中递归解决,有的需要写个Helper方法方法,在Helper方法中进行递归,学习这部分内容的最好方法就是画图细品,自己多写几次代码,然后过段时间再来写这个题目的代码,看有没有完整的思路,多细品,代码能力一定会提升的💪💪💪
以上就是今天的所有内容啦~完结撒花~🥳🎉🎉







2098



被折叠的 条评论
为什么被折叠?



