代码随想录--二叉树章节总结 Part I

news2024/11/28 17:42:03

代码随想录–二叉树章节总结 Part I

1.Leetcode144 前序遍历二叉树

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

解题思路1:使用递归解决。

public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    if (root == null) return list;
    preorder(root, list);
    return list;
}
private void preorder(TreeNode root, ArrayList<Integer> list) {
    if (root == null) return;
    // 先遍历根
    list.add(root.val);
    preorder(root.left, list);
    preorder(root.right, list);
}

解题思路2: 利用栈来模拟递归实现非迭代遍历二叉树

  • 创建一个栈,让根结点入栈
  • 然后开始循环,循环条件是栈不为空。首先让栈中元素出栈,将元素保存list中
  • 然后判断元素是否有右孩子,如果有,则右孩子入栈。
  • 然后判断元素是否有左孩子,如果有,则左孩子入栈。
  • 循环结束后,list中保存了前序遍历的序列
public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    // 如果树为空那么直接返回
    if (root == null) return list;
    // 创建一个栈
    Stack<TreeNode> stack = new Stack<>();
    // 根结点入栈
    stack.push(root);
    // 循环
    while (!stack.isEmpty()) {
        // 出栈
        TreeNode node = stack.pop();
        // 保存结点
        list.add(node.val);
        // 判断这个结点是否有右孩子
        if (node.right != null)
          	stack.push(node.right);
        if (node.left != null)
          	stack.push(node.left);
    }
    return list;
}

image-20230125142453083

2.Leetcode145 后序遍历

解题思路1:利用递归实现后序遍历

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

解题思路2: 使用前序遍历的序列进行一些加工。前序遍历序列为根左右,如果我们变成根右左,然后在进行一次反转,就变成了左右根,就是后序遍历的顺序了。

public List<Integer> preorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    // 如果树为空那么直接返回
    if (root == null) return list;
    // 创建一个栈
    Stack<TreeNode> stack = new Stack<>();
    // 根结点入栈
    stack.push(root);
    // 循环
    while (!stack.isEmpty()) {
        // 出栈
        TreeNode node = stack.pop();
        // 保存结点
        list.add(node.val);

        if (node.left != null)
          	stack.push(node.left);

        if (node.right != null)
          	stack.push(node.right);
    }
    Collections.reverse(list);
    return list;
}

这种遍历方式并不是真正意义的后序遍历,因为访问结点的顺序实际上是根右左

3.Leetcode94 中序遍历

解题思路1: 利用递归实现

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

private void inorder(TreeNode root, ArrayList<Integer> list) {
    if (root == null) return;
    inorder(root.left, list);
    list.add(root.val);
    inorder(root.right, list);
}

解题思路2:利用栈实现非递归方式

  • 创建一个指针,指向根结点,创建一个栈
  • 如果指针不为空,或者栈不空,那么进行循环
  • 在循环中,如果指针不为空,那么就将元素入栈,并将cur指向它的左子树
  • 如果指针为空,那么说明cur指向的元素要么已经没有左子树了,说明cur是当前子二叉树的根结点,因此保存cur到list,并判断cur是否有右子树,如果有,则将cur指向右子树
  • 或者cur可能是某一个子二叉树的右子树,如果此时cur为空,则说明这个子二叉树已经被遍历完毕,需要从栈汇总取新的元素。
public List<Integer> inorderTraversal(TreeNode root) {
    ArrayList<Integer> list = new ArrayList<>();
    if (root == null) return list;
    TreeNode cur = root;
    Stack<TreeNode> stack = new Stack<>();
    while (cur != null || !stack.isEmpty()) {
        // cur 为空的情况主要有遍历到最左边了,已经到头了
        // 或者弹出的元素没有右子树
        if (cur != null) {
            // cur 入栈
            stack.push(cur);
            // 遍历cur是否还有左子树
            cur = cur.left;
        } else {
            // 如果cur已经为空 要么cur没有左子树,那么cur就是当前二叉树的根
            // 既然是中序,并且左子树为空,那么就改轮到cur了
            // 要么cur是某一个结点的右子树,右子树为空,说明这个子树已经遍历完了
            // 那么要从栈中取出新元素
            TreeNode node = stack.pop();
            list.add(node.val);
            if (node.right != null)
              	cur = node.right;
        }
    }
    return list;
}

image-20230125145649442

4.Leetcode102 层序遍历

解题思路:利用队列实现层序遍历

  • 首先将根结点加入队列
  • 只要队列不为空,就出队列,然后将左右子树入队
  • 直到队列为空

需要注意的是,如果需要分层输出每次层的结点,那么就需要记录一下每次入队后队的size,size的目的就是记录每一层的元素个数。如果单纯输出一个层序遍历序列,则不需要记录size

public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> list = new ArrayList<>();
    if (root == null) return list;
    // 创建队列,根结点入队
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        ArrayList level = new ArrayList(size);
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null)
              	queue.offer(node.left);
            if (node.right != null)
              	queue.offer(node.right);
        }
        list.add(level);
    }
    return list;
}

5.Leetcode226 反转二叉树

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

img

示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

img

示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]

解题思路1: 利用递归

  • 如果传入的结点是一个空结点,那么直接返回空即可
  • 否则就判断当前结点是否是叶子结点,如果是叶子结点,则直接返回该结点
  • 否则就递归的先去反转当前结点的左右子树
  • 等左右子树都反转完成后,然后在交换以当前结点为根的树的左右子树即可。
public TreeNode invertTree(TreeNode root) {
    // 如果树为空或者这个树只有一个结点,直接返回
    if (root == null) return null;
    // 如果目前root是一个叶子结点,则直接返回
    if (root.left == null && root.right == null) 
      	return root;
    // 否则就先让其左右子树都进行反转
    invertTree(root.left);
    invertTree(root.right);
    // 都反转完了以后,在反转以root为根的二叉树
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;
    return root;
}

上面的方法是使用后序遍历实现的,这个题也可以使用前序遍历

只需要把invertTree写在交换代码的后面即可。

这个题实际上还可以使用中序遍历。但是两次遍历都需要遍历左子树。图解如下:
image-20230125154804364

伪代码如下:

invertTree(root.left);
swap(root.left, root.right)
invertTree(root.left);

解题思路2: 利用层序遍历实现

和普通的层序遍历代码相同,只不过在出队的时候需要交换出队元素的左右子树

public TreeNode invertTree(TreeNode root) {
    if (root == null) return null;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        // 交换
        TreeNode temp = node.left;
        node.left = node.right;
        node.right = temp;
        if (node.left != null)
          	queue.offer(node.left);
        if (node.right != null)
          	queue.offer(node.right);
    }
    return root;
}

6.Leetcode101 对称二叉树

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

示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false

提示:树中节点数目在范围 [1, 1000] 内 -100 <= Node.val <= 100

解题思路1:利用二叉树层序遍历。但是需要注意的是,即使遍历的某一个结点没有左子树或者右子树,也要入队,就入队一个标志位,标志这个地方为空。然后每一层都所有元素都收集起来,存放到list中。然后判断list是否位回文。如果不是,则返回false。否则等队中元素为空以后,返回true。

public static boolean isSymmetric(TreeNode root) {
    // 如果树中只有一个结点 返回true
    if (root.left == null && root.right == null) return true;
    // 创建队列 层序遍历
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    ArrayList<Integer> list;
    while (!queue.isEmpty()) {
        // 获取这一层的元素个数
        int size = queue.size();
        list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            if (node == null)
              	list.add(-1000);
            else
              	list.add(node.val);
            if (node != null) {
                queue.offer(node.left != null ? node.left : null);
                queue.offer(node.right != null ? node.right : null);
            }
        }

        if (!check(list)) return false;
    }
    return true;
}
private static boolean check(ArrayList<Integer> list) {
    // 判断字符串是不是回文
    int left = 0, right = list.size() - 1;
    while (left < right) {
        int l = list.get(left);
        int r = list.get(right);
        if (l != r)
          	return false;
        left++;
        right--;
    }
    return true;
}

这个题最一开始想的是用字符串来存储每一层的值,然后空的位置填写null。然后判断是否为回文。这样不可行,因为这个循环会遍历到二叉树高度+1的位置,也就是说,最后一次队列中所有元素都为null元素,那么输出的字符串就是nullnullnull,显然不是回文,返回false。而实际上应该返回true。

所以后来使用list代替,根据结点val的取值范围,选择使用-1000来代表空结点。

解题思路2: 利用递归中的后序遍历实现。

  • 首先要比较当前传入的结点的左右子树。
  • 如果左右子树都为空,则返回true
  • 如果左子树为空右子树不为空,或者左子树不为空,右子树为空,则返回false
  • 如果左右子树都不为空,但是值不相等,则返回false
  • 否则就去判断传入结点的左右子树是否满足上述条件。
  • 只有当左右子树都满足条件的时候,以传入结点为根的二叉树才满足条件,返回true
public boolean isSymmetric1(TreeNode root) {
    return compare(root.left, root.right);
}

private boolean compare(TreeNode left, TreeNode right) {

    if (left == null && right != null) {
        return false;
    }
    if (left != null && right == null) {
        return false;
    }

    if (left == null && right == null) {
        return true;
    }
    if (left.val != right.val) {
        return false;
    }
    // 比较外侧
    boolean compareOutside = compare(left.left, right.right);
    // 比较内侧
    boolean compareInside = compare(left.right, right.left);
    return compareOutside && compareInside;
}

下面通过画图展示递归过程,展示了满足条件的树和不满足条件的树。

image-20230126163558640

7.Leetcode104 求二叉树的最大深度

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

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

示例:给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

关于二叉树的深度和二叉树的高度

  • 二叉树中一个结点的高度,指的是从叶子结点到该结点经过的路径数
  • 二叉树中一个结点的深度,指的是从根结点到该结点的的经过的路径数

根结点的高度就是二叉树的最大深度

解题思路1: 利用递归。

  • 如果当前结点为叶子结点,则返回高度为1
  • 否则,则高度就为1+max(当前结点左子树高度,当前结点的右子树高度)
public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    // 如果是叶子结点,直接返回1
    if (root.left == null && root.right == null) return 1;
  	int left = maxDepth(root.left); // 左
  	int right = maxDepth(root.right); // 右
    return 1 + Math.max(left, right); // 中
}

从理论上来说,求二叉树的高度是自底向上进行计算,因此适用于后序遍历;而求深度是从根向下遍历,适用于先序遍历。这个题是求深度,但是使用了后续遍历,主要原因是根结点的高度就是二叉树的最大深度

解题思路2:利用层序遍历来做,对层序遍历代码简单修改即可。

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    if (root.left == null && root.right == null) return 1;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int count = 0;
    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);
        }
        count++;
    }
    return count;
}

8.Leetcode111 求二叉树的最小深度

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

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

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

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

解题思路1: 利用层序遍历,使用一个变量记录层数,只要看这一层是不是满元素状态就可以,如果不是满的,那么就直接返回层数。这个方式实际上不可行,考虑示例2的情况,按照这个方法返回的结果是1,而正确答案是5。

实际上这道题可以使用层序遍历来做,我们使用层序遍历每一个结点,当遍历到第一个叶子结点的时候,直接返回层数,就是这棵树的最小深度

public int minDepth(TreeNode root) {
    if (root == null) return 0;
    if (root.left == null && root.right == null) return 1;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int count = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        count++;
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            // 如果遍历到叶子结点,则直接返回层数即可
            if (node.left == null && node.right == null) return count;
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
    }
    return count;
}

解题思路2: 利用递归的方式。

public int minDepth(TreeNode root) {
    if (root == null) return 0;
    // 如果是叶子结点,直接返回1
    if (root.left == null && root.right == null) return 1;
  	int left = minDepth(root.left); // 左
  	int right = minDepth(root.right); // 右
    return 1 + Math.min(left, right); // 中
}

本来的想法是和上面那道题一样,只需要修改max为min即可,但是发现示例2的结果不对。经过原因分析发现,当输入结点是root的时候,会计算left和right,left为0,right为4,那么最后的结果输出为1,这不不对。也就是说,如果某一个结点只有左孩子或者只有有孩子的时候,我们就不需要再求左右子树深度的最小值了,二是直接返回左孩子或者有孩子深度+1。

image-20230126180356793

正确的递归代码如下:

public int minDepth(TreeNode root) {
    // 如果树为空
    if (root == null) return 0;
    // 如果树中只有一个结点
    if (root.left == null && root.right == null) return 1;
    // 如果这个结点只有左孩子
    if (root.left != null && root.right == null)
      	return minDepth(root.left) + 1;
    // 如果这个结点只有右子树
    if (root.left == null && root.right != null)
      	return minDepth(root.right) + 1;
    // 如果这个结点左右子树都存在
    int left = minDepth(root.left);
    int right = minDepth(root.right);

    return 1 + Math.min(left, right);
}  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c中对宏的理解(面试题)

1、gcc的编译过程&#xff1a;预处理、编译、汇编、链接 预处理&#xff1a;宏替换、删除注释、头文件包含、条件编译 -E &#xff08;不会发生报错&#xff09;生成预编译文件 将 01_code.c文件使用 gcc -E 01_code.c -o 01.i生成预编译文件01.i 可以10行的源文件看见生成800…

【c语言进阶】枚举与联合体的基本知识大全

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我…

1600_Cmake学习笔记_Cmake实践学习

全部学习汇总&#xff1a; GreyZhang/g_cmake: my learning notes for cmake tool. (github.com) 之前考虑过是否要学习Cmake&#xff0c;经过各种对比之后放弃了&#xff0c;我选择了去学习scons。在实际的工作以及学习中scons也的确给了我很大的受益。当时出于各种方面的原因…

PyQt5开发环境搭建 1.1 软件安装

写在前面的话&#xff08;1&#xff09;相对而言&#xff0c;python&#xff0c;PyQt5安装还是比较快的。Qt这个又大又慢。Eric也是需要比较长的时间。&#xff08;2&#xff09;安装失败很正常&#xff0c;多尝试几次&#xff0c;多查查&#xff0c;努力装好软件。安装和配置是…

【Linux】VM与Linux的安装

1.1 VMWare安装 1&#xff09;VMware Workstation Pro安装向导 2&#xff09;VMware Workstation安装的许可协议。 3&#xff09;VMware Workstation安装路径。 4&#xff09;VMware Workstation增强型键盘功能。 5&#xff09;VMware Workstation软件检查更新和帮助完善 6&am…

Linux常见的进程间通信

目录管道pipe匿名管道接口介绍示例代码fifo命名管道接口介绍代码示例匿名管道与命名管道的区别shm共享内存接口介绍相关指令代码示例特点总结信号信号量socket套接字管道 管道是一种较老的&#xff0c;半双工通信方式&#xff0c;即数据只能向一个方向流动&#xff08;即一个进…

力扣 2303. 计算应缴税款总额

题目 给你一个下标从 0 开始的二维整数数组 brackets &#xff0c;其中 brackets[i] [upperi, percenti] &#xff0c;表示第 i 个税级的上限是 upperi &#xff0c;征收的税率为 percenti 。税级按上限 从低到高排序&#xff08;在满足 0 < i < brackets.length 的前提…

spring笔记下(AOP、事务管理)

一、AOP概述 1. AOP介绍 AOP(Aspect Oriented Programming)&#xff1a;面向切面编程&#xff0c;一种编程范式&#xff0c;指导开发者如何组织程序结构&#xff0c;是oop的延续。&#xff08;OOP面向对象编程&#xff09; AOP作用&#xff1a;在不惊动原始设计的基础上为其进…

LeetCode刷题模版:221 - 230

目录 简介221. 最大正方形222. 完全二叉树的节点个数223. 矩形面积224. 基本计算器【未理解】225. 用队列实现栈226. 翻转二叉树227. 基本计算器 II【未理解】228. 汇总区间229. 多数元素 II230. 二叉搜索树中第K小的元素结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有…

【学习打卡 Free-Excel 】Task6 查找函数

文章目录1. VLOOKUP用法示例注意情况一情况二2. 通配符用法示例3.XLOOKUP用法匹配类型搜索模式1. VLOOKUP 用法 【VLOOKUP函数】VLOOKUP&#xff08;要查找的值&#xff0c;查找区域&#xff0c;要返回的结果在查找区域的第几列&#xff0c;精确匹配或近似匹配&#xff09;示…

JAVA Web 常见问题解决方案(持续更新,欢迎投稿常见问题)

大家有什么问题未解决的可以试图联系博主&#xff0c;群号在主页的详细资料 都是博主自己学习过程中遇到的问题&#xff0c;大家有什么常见问题欢迎Git 问题集目录 找到适合自己版本的pom依赖 IDEA实体类快速创建 get(),set(),toString()方法 报错Cannot find class: com.mysq…

【图卷积网络】03-空域卷积介绍 (一)

注&#xff1a;本文为3.1-3.2 空域卷积视频笔记&#xff0c;仅供个人学习使用 1、谱域图卷积 1.1 回顾 上篇博客【图卷积神经网络】02-谱域图卷积介绍讲到了三个经典的谱域图卷积&#xff1a; SCNN用可学习的对角矩阵来代替谱域的卷积核。 ChebNet采用Chebyshev多项式代替谱…

webpack使用Ammo.js - 在react中使用Ammo.js

真实麻烦啊[我的项目仓库 Next.js项目 仅供参考](https://gitee.com/honbingitee/three-template-next.js/tree/feature%2Fphysics/)本文展示使用ammo.wasm.js 结合ammo.wasm.wasm的wasm版本使用方法1. 配置webpack2. 导出Ammo 修改ammo.wasm.js文件3. 删除语句 通过查找 this.…

【Flink】详解Flink的八种分区

简介 Flink是一个流处理框架&#xff0c;一个Flink-Job由多个Task/算子构成&#xff0c;逻辑层面构成一个链条&#xff0c;同时Flink支持并行操作&#xff0c;每一个并行度可以理解为一个数据管道称之为SubTask。我们画图来看一下&#xff1a; 数据会在多个算子的SubTask之间相…

【奇妙的数据结构世界】用图像和代码对链表的使用进行透彻学习 | C++

第九章 链表 目录 第九章 链表 ●前言 ●一、链表是什么&#xff1f; 1.简要介绍 2.具体情况 ●二、链表操作的关键代码段 1.类型定义 2.常用操作 ●总结 前言 简单来说&#xff0c;数据结构是一种辅助程序设计并且进行优化的方法论&#xff0c;它不仅讨论数据的存储与处…

打工人必知必会(一)——规章制度保险劳动合同变更

目录 参考 1、规章制度的生效要件 2、工资的发放形式 3、社会保险的基本规定 4、基本养老保险 5、医疗保险、失业保险、工伤保险、生育保险 6、劳动合同的变更 第一节 协商变更劳动合同 第二节 单方变更劳动合同 参考 《HR全程法律顾问&#xff1a;企业人力资源管理高…

5-6中央处理器-多处理器系统硬件多线程

文章目录一.多处理器系统&#xff08;一&#xff09;计算机体系结构分类1.单指令单数据流SISD2.单指令多数据流SIMD3.多指令单数据流MISD4.多指令多数据流MIMD&#xff08;1&#xff09;(共享内存)多处理器系统/多核处理器&#xff08;2&#xff09;多计算机系统&#xff08;二…

逆水寒魔兽老兵服副本攻略及代码分析(英雄武林风云录,后续更新舞阳城、扬州、清明等副本攻略)

文章目录一、武林风云录1&#xff09;老一&#xff1a;陈斩槐&#xff08;只有四个机制&#xff0c;dps压力不大&#xff0c;留爆发打影子就行&#xff09;&#xff08;1&#xff09;点名红色扇形区域&#xff08;2&#xff09;点名红色长条&#xff0c;注意最后还有一段大劈&a…

MongoDB入门(特点,使用场景,命令行操作,SpringData-MongoDB)

今天我们将通过这一篇博客来了解MongoDB的体系结构&#xff0c;命令行操作和在JAVA 当中使用SpringData-MongoDB 来 操作MongoDB。 如果没有安装的小伙伴 可以看一下 这篇文章 (59条消息) 开源的文档型数据库–MongoDB&#xff08;安装&#xff09;_一切总会归于平淡的博客-CS…

LeetCode[128]最长连续序列

难度&#xff1a;中等题目&#xff1a;给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。请你设计并实现时间复杂度为 O(n)的算法解决此问题。示例 1&#xff1a;输入&#xff1a;nums [100,4,2…