二叉树刷题(JAVA)

引入:


递归是一种在函数定义中使用函数自身的方法。它是一种常见的编程技巧,用于解决可以被分解为相同问题的子问题的问题。

递归函数通常包含两个部分:基本情况和递归情况

基本情况是指递归函数停止调用自身的条件。当满足基本情况时,递归函数将不再调用自身,而是返回一个特定的值或执行特定的操作

递归情况是指递归函数调用自身解决更小规模的子问题。通过不断地调用自身,递归函数可以将原始问题分解为更小的子问题,直到达到基本情况。

让我们再看二叉树的遍历,其实它的结构大概是这样(基本框架):

public void traverse(TreeNode root) {
        //前序遍历的位置
        traverse(root.left);
        //中序遍历的位置
        traverse(root.right);
        //后续遍历的位置
    }

例1:二叉树的前序遍历:

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

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

题目链接:144. 二叉树的前序遍历 - 力扣(LeetCode)

import java.util.LinkedList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {}

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

前序遍历


public class Solution {

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>(); // 在这里初始化列表
        if (root == null) {
            return list; // 如果根节点为空,返回空列表
        }
        // 直接进行递归调用
        list.add(root.val); // 访问当前节点
        list.addAll(preorderTraversal(root.left)); // 遍历左子树
        list.addAll(preorderTraversal(root.right)); // 遍历右子树

        return list; // 返回结果列表
    }
}

中序遍历


public class Solution {

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>(); // 在这里初始化列表
        if (root == null) {
            return list; // 如果根节点为空,返回空列表
        }
        // 直接进行递归调用
        list.addAll(preorderTraversal(root.left)); // 遍历左子树
        list.add(root.val); // 访问当前节点
        list.addAll(preorderTraversal(root.right)); // 遍历右子树
        

        return list; // 返回结果列表
    }
}

后序遍历


public class Solution {

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>(); // 在这里初始化列表
        if (root == null) {
            return list; // 如果根节点为空,返回空列表
        }
        // 直接进行递归调用
        list.addAll(preorderTraversal(root.left)); // 遍历左子树
        list.addAll(preorderTraversal(root.right)); // 遍历右子树
        list.add(root.val); // 访问当前节点

        return list; // 返回结果列表
    }
}

例2:N叉树的前序遍历

注:由于N叉树(不止包含左右子节点,可能有多个)的特性,其没有中序遍历,其大致框架是:

class Solution{
public void traverse(TreeNode root){
List<Ingeter> list=new LinkedList<>();
if(root==null){
return list;
}
list.add(root);
traverse(root.left);
traverse(root.right);

return list;
}
}

  public void traverse(TreeNode root){
        if(root == null){
            return ;
        }
 
        //前序遍历的位置
        for(Node child : root.children){
            traverse(child);
        }
           //后序遍历的位置
        }

题目:

给定一个 n 叉树的根节点  root ,返回 其节点值的 前序遍历 。

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

题目链接:589. N 叉树的前序遍历 - 力扣(LeetCode)

题解:

class Solution {

    public List<Integer> preorder(Node root) {
    List<Integer> list=new LinkedList<>();
    prehelp(root,list);
    return list;
    }

    public void prehelp(Node root,List<Integer> list){
        if(root==null){
        return ;
     }
     list.add(root.val);
     //这一步添加了列表元素
     for(Node n:root.children){
     prehelp(n,list);
    }
    
    }    
}class Solution {

    public List<Integer> preorder(Node root) {
    List<Integer> list=new LinkedList<>();
    prehelp(root,list);
    return list;
    }

    public void prehelp(Node root,List<Integer> list){
        if(root==null){
        return ;
     }
     list.add(root.val);
     //这一步添加了列表元素
     for(Node n:root.children){
     prehelp(n,list);
    }
    
    }    
}

关于最大最小使用三目操作符与Math.max()操作符的建议:

  • 当 leftHeight 和 rightHeight 不相等时,条件运算符可能导致返回不准确的深度(例如如果 leftHeight 更大而你错误地选择了它),因此在不同情况下可能会出现问题。
  • Math.min 则始终保证选择最小的深度

虽然在特定情况下(如 leftHeightrightHeight 相等时)两种方法返回相同的结果,但 Math.min 方法更安全、可读性更好。因此,在实际编程中,推荐使用 Math.min 来确保准确性和代码的清晰度

例3:二叉树的最大深度:


题目:

给定一个二叉树 root ,返回其最大深度。 

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

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

题目链接:104. 二叉树的最大深度 - 力扣(LeetCode)

法一:通过二叉树的遍历将其转化为左右子树的最大深度+1(自身)的方式进行求解:

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        } else {
            int leftHeight = maxDepth(root.left);
            int rightHeight = maxDepth(root.right);
            return (leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1);
        }
    }
}

例4:二叉树的最小深度 


题目:

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

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。

题目链接:111. 二叉树的最小深度 - 力扣(LeetCode))

题解:

法一:分解问题,不断求左右子树最小值:

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        } 
        else if(root.left==null&&root.right!=null){
        return minDepth(root.right)+1;
        }
        else if(root.right==null&&root.left!=null){
            return minDepth(root.left)+1;
        }
        
        else {
            int leftHeight = minDepth(root.left);
            int rightHeight = minDepth(root.right);
            return Math.min(leftHeight, rightHeight) + 1;
        }
    }
}

错误原因:

没有考虑到,若左右子树有一树为空,那么有一颗树的返回值就是0

而最后用Math.min获取了元素的最小值,就会返回0+1

法二:迭代思想:与最小深度类似,但这里需要多一个判断是否是叶子节点,是叶子节点是我们才刷新最小值,不然开始就是最小值后续不会在改变,小细节,开始就定义一个极大值,为后续的找最小值做准备


class Solution {
     int depth = 0;
     int res = Integer.MAX_VALUE;
    public  void traverse(TreeNode root){
        if(root == null){
            return ;
        }
        depth++;
        if(root.left == null && root.right == null){
            res = Math.min(res,depth);
        }
        traverse(root.left);
        traverse(root.right);
        depth--;
    }
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        traverse(root);
        return res;
    }

}

例5:N叉树的最大深度:

题目:

给定一个 N 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。

题目链接

559. N 叉树的最大深度 - 力扣(LeetCode)

题解:与二叉树的最大深度基本一致,就是要遍历所有子树:


class Solution {
   public int maxDepth(Node root) {
        if(root==null){
            return 0;
        }
        int maxChildDepth=0;
        for(Node n:root.children){
            int childDepth=maxDepth(n);
          maxChildDepth=Math.max(childDepth,maxChildDepth);
        }
         return maxChildDepth+1;
    }
}

主要没想到怎么获取每个子树的最大值

可通过将每次获取到的最大值让max这个数保存起来,后用Math.max()获取最大值,这样获取最大深度

例6:左叶子之和:

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

题目链接:404. 左叶子之和 - 力扣(LeetCode)

题解:通过给函数传一个boolean值通过二叉树的遍历来判断是否是左叶子节点。然后将是左叶子节点的值相加

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        return root != null ? dfs(root) : 0;
    }

    public int dfs(TreeNode node) {
        int ans = 0;
        if (node.left != null) {
            ans += isLeafNode(node.left) ? node.left.val : dfs(node.left);
        }
        if (node.right != null && !isLeafNode(node.right)) {
            ans += dfs(node.right);
        }
        return ans;
    }

    public boolean isLeafNode(TreeNode node) {
        return node.left == null && node.right == null;
    }
}

为什么没有像到:

想的太碎了,将点分为了多个:如左子节点为空,右子节点不为空

左子节点不为空,右子节点为空

左右子节点都不为空

左右子节点都为空

思路总结:

1.单独抽象一个方法,判断是否为叶子节点

2.递归调用,判断每一个左子节点:左子树(左子节点)不为空

                                                          是叶子节点,累加,不是 继续调用函数

                                                          右子树不为空,且不是叶子节点

                                                          调用函数

例7:翻转二叉树:

题目:

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

题目链接:226. 翻转二叉树 - 力扣(LeetCode)

题解:在前序遍历的基础上交换左右子树即可(图解):


class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null){
            return null;
        }
      //交换左右子树
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }
}
为什么没有想到:
1.想到了对称二叉树
2.将
  1. TreeNode temp = root.left;

  2. root.left = root.right;

  3. root.right = temp;

这段代码理解成了节点互换,并没有互换左右子树

例8: 路径总和:


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

题目链接:112. 路径总和 - 力扣(LeetCode)

题解:法一:在前序遍历位置记录路径的和(节点的val值累加),后序遍历的位置删除路径的和(节点的val值减少):

采用递归

class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        if (root == null) {
            return false; // 基础条件:如果节点为空,返回 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); // 递归检查左子树和右子树
    }
}

段代码实现了一个名为 hasPathSum 的方法,用于判断一棵二叉树是否存在从根节点到某个叶子节点的路径,其路径上所有节点值的和等于给定的目标值 sum

例9:路径总和II:


题目:

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

题目链接:113. 路径总和 II - 力扣(LeetCode)

题解:遍历一遍二叉树,就可以把所有符合条件的路径找出来。为了维护经过的路径,在进入递归的时候要在 path 列表添加节点,结束递归的时候删除节点。

class Solution {
    List<List<Integer>> res = new LinkedList<>();
 
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if (root == null) return res;
        traverse(root, sum, new LinkedList<>());
        return res;
    }
 
    // 遍历二叉树
    private void traverse(TreeNode root, int sum, LinkedList<Integer> path) {
        if (root == null) return;
 
        int remain = sum - root.val;
 
        if (root.left == null && root.right == null) {
            if (remain == 0) {
                // 找到一条路径
                path.addLast(root.val);
                res.add(new LinkedList<>(path));
                path.removeLast();
            }
            return;
        }
 
        // 维护路径列表
        path.addLast(root.val);
        traverse(root.left, remain, path);
        path.removeLast();
 
        path.addLast(root.val);
        traverse(root.right, remain, path);
        path.removeLast();
    }
}


例10:二叉树展开为链表:


题目:

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。

题目链接:114. 二叉树展开为链表 - 力扣(LeetCode)

题解:只要运用二叉树的递归遍历框架即可;后者的关键在于明确递归函数的定义,然后利用这个定义,这题就属于后者,flatten 函数的定义如下:给 flatten 函数输入一个节点 root,那么以 root 为根的二叉树就会被拉平为一条链表。流程:

1、将 root 的左子树和右子树拉平。

2、将 root 的右子树接到左子树下方,然后将整个左子树作为右子树。

class Solution {
    // 定义:将以 root 为根的树拉平为链表
    public void flatten(TreeNode root) {
        // base case
        if (root == null) return;
        // 先递归拉平左右子树
        flatten(root.left);
        flatten(root.right);
 
        /****后序遍历位置****/
        // 1、左右子树已经被拉平成一条链表
        TreeNode left = root.left;
        TreeNode right = root.right;
 
        // 2、将左子树作为右子树
        root.left = null;
        root.right = left;
 
        // 3、将原先的右子树接到当前右子树的末端
        TreeNode p = root;
        while (p.right != null) {
            p = p.right;
        }
        p.right = right;
 
 
 
    }
}

  到这里,竹竹零就要和大家说再见了🍕🍕🍕

如果您觉得有失偏颇请您在评论区指正,如果您觉得不错的话留个好评再走吧!!

您的鼓励就是对我最大的支持!  ! !

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

流体力学笔记

目录 1、名词2、湍流与涡流3 涡激振动4 压力面与吸力面参考&#xff1a;[空气动力学的“他山之石”](https://zhuanlan.zhihu.com/p/412542513) 1、名词 转列&#xff1a;transition 涡脱落&#xff1a;vortex shedding 涡分离&#xff1a;vortex rupture 气动噪声&#xff1a…

代码训练营 day39|0-1背包问题,LeetCode 416

前言 这里记录一下陈菜菜的刷题记录&#xff0c;主要应对25秋招、春招 个人背景 211CS本CUHK计算机相关硕&#xff0c;一年车企软件开发经验 代码能力&#xff1a;有待提高 常用语言&#xff1a;C 系列文章目录 第九章 动态规划part03 文章目录 前言系列文章目录第九章 动态…

GDAL+C#实现矢量多边形转栅格

1. 开发环境测试 参考C#配置GDAL环境&#xff0c;确保GDAL能使用&#xff0c;步骤简述如下&#xff1a; 创建.NET Framework 4.7.2的控制台应用 注意&#xff1a; 项目路径中不要有中文&#xff0c;否则可能报错&#xff1a;can not find proj.db 在NuGet中安装GDAL 3.9.1和G…

达梦数据库性能优化

1、SQL执行计划 拿到一条SQL的时候&#xff0c;首先要下达梦手册中提出的有效SQL规范&#xff0c;及是否命中了特殊OR子句的不规范&#xff0c;是否用了复杂的正则表达式&#xff0c;避免重复很高的索引&#xff0c;UINON ALL 是否可以替换UNION操作等,某些场景INSTR函数导致的…

ParallelsDesktop20最新版本虚拟机 一键切换系统 游戏娱乐两不误

让工作生活更高效&#xff1a;Parallels Desktop 20最新版本虚拟机的神奇之处 大家好&#xff01;&#x1f44b; 今天我要跟大家安利一款让我工作效率飞升的神器——Parallels Desktop 20最新版本虚拟机。作为一个日常需要在不同操作系统间来回穿梭的人&#xff0c;这款软件简直…

【升华】python基础包NumPy学习

NumPy是使用Python进行科学计算的基础软件包。除其他外&#xff0c;它包括&#xff1a; 功能强大的N维数组对象。精密广播功能函数。集成 C/C和Fortran 代码的工具。强大的线性代数、傅立叶变换和随机数功能。 # 1、安装包 $ pip install numpy# 2、进入python的交互式界面 $…

从零开始学PHP之helloworld

前言 每一门编程语言的第一个程序就是输出hell world&#xff08;别杠&#xff0c;杠就是你对&#xff09; 开始 上一篇讲完了开发环境的安装&#xff0c;这次讲编辑器的安装&#xff0c;顺带完成上一篇的作业&#xff08;输出hello world&#xff09; 安装PHPstorm 我用的…

前端布局与响应式设计综合指南(末)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Css篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Css篇专栏内容:前端布局与响应式设计综合指南(末) 目录 61、为什么要初始化CSS样式 62、CSS3 有哪些新特性 63、…

【python】NumPy(三):文件读写

目录 ​前言 NumPy 常见IO函数 save()和load() savez() loadtxt()和savetxt() 练习 前言 在数据分析中&#xff0c;我们经常需要从文件中读取数据或者将数据写入文件&#xff0c;常见的文件格式有&#xff1a;文本文件txt、CSV格式文件&#xff08;用逗号分隔&#xff…

vue-router钩子中调用ElMessage等样式出错

升级 vue3.5 时遇到奇怪的问题, 页面点击离开没反应 经过排查, 是以下几点相互作用导致此问题 vue 有应用上下文的概念, 例如 runWithContext API,vue-router 在调用钩子时会获取 vue 的应用上下文element-plus 在唤起弹窗时会从 parent 或 应用上下文上拿到 config 信息eleme…

大数据环境 hbase+ phoenix idea

1、选择jdk 2、添加驱动 3、添加数据源

【精选】基于javaweb的流浪动物领养系统(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

《重置MobaXterm密码并连接Linux虚拟机的完整操作指南》

目录 引言 一、双击MobaXterm_Personal_24.2进入&#xff0c;但是忘记密码。 那么接下来请跟着我操作。 二、点击此链接&#xff0c;重设密码。 三、下载完成后&#xff0c;现在把这个exe文件解压。注意解压要与MobaXterm_Personal_24.2.exe在同一目录下哦&#xff0c;不然…

从零入门AI篡改图片检测(金融场景)#Datawhale十月组队学习

1.大赛背景 在全球人工智能发展和治理广受关注的大趋势下&#xff0c;由中国图象图形学学会、蚂蚁集团、云安全联盟CSA大中华区主办&#xff0c;广泛联合学界、机构共同组织发起全球AI攻防挑战赛。本次比赛包含攻防两大赛道&#xff0c;分别聚焦大模型自身安全和大模型生成内容…

pc轨迹回放制作

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;pc轨迹回放制作 主要内容&#xff1a;制作车辆轨迹操作页&#xff0c;包括查询条件、动态轨迹回放、车辆轨迹详情表单等 应用场景&#xff1a;车辆…

sqli-labs less-25a

Sqli-labs less-25a 判断注入类型&#xff0c;列数及注入点 构造 http://192.168.140.130/sq/Less-25a/?id1 回显正常 http://192.168.140.130/sq/Less-25a/?id1’ 报错 构造 http://192.168.140.130/sq/Less-25a/?id1 and 11 回显正常 http://192.168.140.130/sq/Less-25a…

RabbitMQ 入门(三)SpringAMQP五种消息类型(Basic Queue)

一、Spring AMQP 简介 SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址&#xff1a;https://spring.io/projects/spring-amqp SpringAMQP提供了三个功能&#xff1a; - 自动…

唐布拉不是家,而我途径它(一)

唐布拉不是家&#xff0c;而我途径它 旅途是一本书&#xff0c;风吹哪页读哪页&#xff0c;笔搁哪句写哪句。 最后一站在唐布拉的尼勒克&#xff0c;晚上11点才到&#xff0c;车停在门口&#xff0c;打开车门就看到这句话&#xff1a; 希望你快乐 今天 明天 天天刚下完雨&am…

2024年诺贝尔物理学奖揭晓:AI背后的“造梦者”是谁?

想象一下&#xff0c;你早上醒来&#xff0c;智能音箱为你播放天气和新闻&#xff0c;中午你用手机刷视频&#xff0c;精准的推荐内容简直和你心有灵犀&#xff0c;晚上回家&#xff0c;自动驾驶汽车安全地把你送回家。这一切看似理所当然&#xff0c;背后却有一双无形的手推动…

双向链表常见接口实现

一 . 链表的分类 链表的结构非常多样,以下情况组合起来就有8种( 2 * 2 * 2 ) 链表结构 : 1.1 单向/双向 1 . 单向 : 只能往一个方向遍历 (仅有一个指针 --> 指向下一个结点的地址), 如下图 : 只能从d1找到d2 , d2 找不到d1 2 . 双向 : 能从两个方向遍历 ( 有指向下一个结点…