【代码随想录训练营】【Day17】第六章|二叉树|110.平衡二叉树|257. 二叉树的所有路径|404.左叶子之和

news2024/11/18 9:49:09

平衡二叉树

题目详细:LeetCode.110

由题可知:一个平衡二叉树需要满足,其每个节点的左右两个子树的高度差的绝对值不超过 1 。

我们可以依照题意,直接来一波模拟:

  • 利用层序遍历(或其他遍历方法)遍历每一个节点
  • 通过计算每一个节点的左右子树的高度差来判断是否为平衡二叉树

那么这道题的难点就在于:如何计算树的高度?亦或是如何计算当前节点的高度?

在这里插入图片描述

由图及二叉树的概念可知,二叉树的高度和深度是两个不同的定义:

  • 二叉树的深度:指从根节点到当前节点的最长简单路径边数(从上往下计算)
  • 二叉树的高度:指从当前节点到叶子节点的最长简单路径边数(从下往上计算)

由此我们可以得到计算二叉树深度和高度的遍历方式:

  • 计算二叉树的深度,需要从上到下去访问节点,所以使用前序遍历(根左右)
  • 计算二叉树的高度,需要从下到上去访问节点,所以使用后序遍历(左右根)

Java解法(模拟,迭代,层序遍历节点,后序遍历计算树的高度):

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(null == root) return true;
        return this.bfs(root);
    }

    public int getHeight(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        if(null != root) stack.push(root);
        int res = 0, height = 0;
        while(!stack.isEmpty()){
            TreeNode node = stack.pop();
            if(null != node){
                stack.push(node);
                stack.push(null);
                height++;
                if(null != node.right) stack.push(node.right);
                if(null != node.left) stack.push(node.left);
            }else{
                node = stack.pop();
                height--;
            }
            res = Math.max(res, height);
        }
        return res;
    }

    public boolean bfs(TreeNode root){
        Queue<TreeNode> queue = new LinkedList<>();
        if(null != root) queue.offer(root);
        while(!queue.isEmpty()){
            int n = queue.size();
            while(n-- > 0){
                TreeNode node = queue.poll();
                if(Math.abs(this.getHeight(node.left) - this.getHeight(node.right)) > 1) return false;
                if(null != node.left) queue.offer(node.left);
                if(null != node.right) queue.offer(node.right);
            }
        }
        return true;
    }
}

通过之前的练习和解题过程,我们可以发现:

  • 计算二叉树的高度的算法和计算二叉树的深度的算法很相似
  • 计算二叉树的深度使用后序遍历也可也得到正确的结果
  • 计算二叉树的高度差,其实和计算二叉树的深度差是一样的结果,只是在定义上不同

当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。
虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。
众所周知,都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!

迭代法虽然能够非常清晰的根据平衡二叉树的特点来解题,但是在遍历过程中存在着许多重复的计算,例如重复计算节点的高度。

用递归的方法来实现回溯的过程,能够更有效率地来解决这道题:

Java解法(递归):

class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null){
            return true;
        }
        int leftHeight = getHeight(root.left, 1);
        int rightHeight = getHeight(root.right, 1);
        return Math.abs(leftHeight - rightHeight) <= 1 
            && isBalanced(root.left) 
            && isBalanced(root.right);
    }

    private int getHeight(TreeNode root, int height){
        if(root == null){
            return height;
        }
        int leftHeight = getHeight(root.left, height+1);
        int rightHeight = getHeight(root.right, height+1);
        return Math.max(leftHeight, rightHeight);
    }
}

上述代码只是将模拟的过程转为递归写法,虽然利用&&的短路效应也得到了提升算法效率的目的,但不能很明显的体现出回溯的特点。

而且如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。

所以如果已经不是二叉平衡树了,可以返回 -1 来标记当前节点已经不符合平衡树的规则了,不需要往后再进行递归操作。

Java解法(递归,优化):

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(null == root) return true;
        return this.getHeight(root) == -1 ? false : true;
    }

    public int getHeight(TreeNode root){
        if(null == root) return 0;
        int leftHeight = this.getHeight(root.left);
        if(leftHeight == -1) return -1;
        int rightHeight = this.getHeight(root.right);
        if(rightHeight == -1) return -1;
        // 分别求出其左右子树的高度,然后如果差值绝对值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
        return Math.abs(leftHeight - rightHeight) > 1 ? -1 : 1 + Math.max(leftHeight, rightHeight);
    }
}

二叉树的所有路径

题目详细:LeetCode.257

题目要求找到从根节点到叶子节点的所有路径,使用前序遍历,能够方便地让父节点指向孩子节点,找到对应的路径。

确定了遍历顺序为前序遍历后,接下来就是对节点的处理逻辑:

  • 要求输出二叉树从根节点到叶子节点的路径,那么我们就需要在遍历过程中,利用列表记录路径上经过的节点
  • 当遇到叶子节点时,则可确定一条路径,按照要求的格式将路径列表转为字符串,保存到结果集中
  • 确定了一条路径后,我们需要回溯,也就是回退一个节点并尝试寻找另一条路径
    • 如果不进行回溯,则可能在其他路径记录中出现重复的节点
  • 直到每一个节点都经过了访问和回溯过程,没有其他节点可以访问时,回溯停止,说明找到了二叉树的所有路径。

Java解法(递归,回溯过程明显化):

class Solution {
    private List<String> ans = new ArrayList<>();

    public List<String> binaryTreePaths(TreeNode root) {
        List<TreeNode> path = new ArrayList<>();
        if(null == root) return this.ans;
        this.traversal(root, path);
        return ans;
    }

    public void traversal(TreeNode root, List<TreeNode> path){
        if(null == root) return;
        path.add(root);
        if(null == root.left && null == root.right){
            String path_str = path.stream().map(node -> String.valueOf(node.val)).collect(Collectors.joining("->"));
            this.ans.add(path_str);
            return;
        }
        if(null != root.left){
            this.traversal(root.left, path);
            // 回溯
            path.remove(path.size() - 1);
        }
        if(null != root.right){
            this.traversal(root.right, path);
            // 回溯
            path.remove(path.size() - 1);
        }
    }
}

递归完,就要做回溯,因为 path 是引用传递,需要删节点后才能加入新的节点,否则会重复出现其他路径的节点。
回溯和递归应该是一一对应的,有一个递归,就要有一个回溯。

在之前的许多练习中,其实用到了递归也就已经用到了回溯,只是没办法很明显的体现出来。

所以在这道题的解题过程中,我们用List来存储路径上的节点,其实也可以用String直接来存储到达叶子节点的路径,只是用List来存储之后,在每次递归完成后都需要remove已经过的节点,能够非常明显地看到回溯的过程。

那么假设我们用String来作为递归参数,存储路径的话,应该要这么写:

Java解法(递归,简洁版,隐藏了回溯过程):

class Solution {
    private List<String> ans = new ArrayList<>();

    public List<String> binaryTreePaths(TreeNode root) {
        this.traversal(root, null);
        return ans;
    }

    public void traversal(TreeNode root, String path){
        if(null == path){
            path = String.valueOf(root.val);
        }else{
            path += String.valueOf(root.val);
        }
        if(null == root.left && null == root.right){
            this.ans.add(path);
            return;
        }
        if(null != root.left){
            this.traversal(root.left, path + "->");
        }
        if(null != root.right){
            this.traversal(root.right, path + "->");
        }
    }
}

左叶子之和

题目详细:LeetCode.404

由题目和示例可知:

  • 求叶子节点之和,但这个叶子节点,要求是树的左节点
  • 空树视为没有节点,只有一个节点的树的根节点不视作左节点。

在递归函数中,我增加了一个标识参数 isLeft ,来标识当前节点是否为树的左节点

那么我们只需要遍历树中的每一节点,找到满足以下两个条件的节点即可:

  • 找到叶子节点:root.left == null && root.right == null
  • 节点是左节点:isLeft == true

累计满足条件的节点的属性数值即可得到左叶子之和,这样的递归方式适合全部的遍历顺序。

Java解法(递归,易理解版):

class Solution {
    public int sum = 0;

    public int sumOfLeftLeaves(TreeNode root) {
        this.traversal(root, false);
        return this.sum;
    }

    public void traversal(TreeNode root, boolean isLeft){
        if(root == null) return;
        if(root.left == null && root.right == null){
            if(isLeft) sum += root.val;
            return;
        }
        this.traversal(root.left, true);
        this.traversal(root.right, false);
    }
}

当然如果要应扣遍历顺序的话,因为是求左节点之和,所以优先处理的应该是左节点,所以一般采用后序遍历的顺序(左右根)。

不过解题的思路是相似的,只是在遍历过程中,优先访问了左节点,并判断该左节点是不是叶子节点,如果是叶子节点则累计数值:

Java解法(后序遍历):

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        return this.traversal(root);
    }

    public int traversal(TreeNode root){
        if(root == null) return 0;
        int leftSum = traversal(root.left);
        TreeNode leftNode = root.left;
        if(leftNode != null && leftNode.left == null && leftNode.right == null){
            leftSum = leftNode.val;
        }
        int rightSum = traversal(root.right);
        int sum = leftSum + rightSum;
        return sum;
    }
}

Java解法(后序遍历,简约版):

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        return this.traversal(root);
    }

    public int traversal(TreeNode root){
        if(root == null) return 0;
        int leftNum = 0;
        TreeNode leftNode = root.left;
        if(leftNode != null && leftNode.left == null && leftNode.right == null){
            leftNum = leftNode.val;
        }
        return leftNum + this.traversal(root.left) + this.traversal(root.right);
    }
}

这一天天地,感觉越来越累了:

昏昏此身何所似,恰似芭蕉骤雨中。

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

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

相关文章

@所有人,OceanBase DevCon • 2023来啦

本文by&#xff1a;即将与大家见面的 OceanBase 2010 年&#xff0c;OceanBase 第一个版本诞生。在过去的十三年里&#xff0c;我们的产品技术&#xff0c;从支付宝走向众多企业&#xff0c;跟随着开源和云的成长&#xff0c;逐渐成为开发者喜欢的数据库。 2023 年 3 月 25 日…

MySQL的日志详解

目录 一.介绍 日志分类 二.错误日志 三.二进制日志—binlog 概述 日志格式 操作 四.查询日志 五.慢查询日志 一.介绍 在任何一种数据库中&#xff0c;都会有各种各样的日志&#xff0c;记录着数据库工作的方方面面&#xff0c;以帮助数据库管理员追踪数据库曾经发生过的…

IP路由基础

——IP路由基础&#xff08;IA&#xff09;—— ​​​​​​​HCIA全套笔记已经上线&#xff08;arpAAAvlanTrunk链路聚合vlan间通信ACL广域网技术以太网交换...........)_孤城286的博客-CSDN博客 目录 ——IP路由基础&#xff08;IA&#xff09;—— &#xff08;1&#…

【Debug】Centos 7 下部署 ElasticSearch 及 Kibana 时踩过的坑

Windows 电脑安装的 Centos 7 都是 X86_64版本, 但是 MAC 电脑 M1 芯片安装的是 arm 64 版本的 Centos 7, 这就导致有些镜像的安装可能会出现问题. 如果拉取速度比较慢, 修改镜像源, 如我的镜像源如下: 执行创建或修改镜像源指令: vim /etc/docker/daemon.json, 然后将下面的内…

【CMU15-445数据库】bustub Project #2:B+ Tree(上)

&#xff08;最近两个月学校项目有亿点忙&#xff0c;鸽得有点久&#xff0c;先来把 Project 2 补上&#xff09; 本节实验文档地址&#xff1a;Project #2 - BTree Project 2 要实现的是数据结构课上都会讲的一个经典结构 B 树&#xff0c;但是相信大多数的同学&#xff08;…

vue中,给一个URL地址,利用FileSaver.js插件下载文件到本地

①首先下载 FileSaver.js 插件 npm install file-saver --save ②在需要的.vue页面引入 import { saveAs } from file-saver 在HTML中引入 <script src"https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> //Fil…

k8s 安装dashboard

前言 上一篇中将k8s简单部署安装上了&#xff0c;这篇接着安装下dashboard。 具体步骤 下载yaml文件 wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.0/aio/deploy/recommended.yaml注意&#xff1a;这里使用的版本是v2.5.0&#xff0c;这个要和k8s的版…

褪去大厂光环下的功能测试,出去面试自动化居然一问三不知......不淘汰你淘汰谁呢

在一家公司待久了技术能力反而变弱了&#xff0c;原来的许多知识都会慢慢遗忘&#xff0c;这种情况并不少见。 一个京东员工发帖吐槽&#xff1a;感觉在大厂快待废了&#xff0c;出去面试问自己接口环境搭建、pytest测试框架&#xff0c;自己做点工太久都忘记了。平时用的时候…

CCF-CSP真题《202212-2 训练计划》思路+python题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 试题编号&#xff1a;202212-2试题名称&#xff1a;训练计划时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 问题背景 西西艾弗岛荒野求生大赛还有 n 天开幕&#xf…

总结Anisble中的任务执行控制并练习

文章目录一、循环1.简单循环2.循环散列或字典列表二、条件三、触发器四、处理失败任务1.ignore_errors2.force_handlers3.changed_when4.failed_when5.block五、 练习建立大小为1500M名为/dev/sdb1的设备利用ansible循环安装且开启vsftpd&#xff0c;apache&#xff0c;dns&…

java8新特性【2023】

Lambda表达式 新的一套语法规则 是一个匿名函数 Testpublic void test1(){Runnable r1 new Runnable(){Overridepublic void run() {System.out.println("线程A");}};r1.run();System.out.println("");Runnable r2 () -> System.out.println("…

linux 安装rabbitmq 文档

1、下载rabbitMQ https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.15对应的erlang https://packagecloud.io/app/rabbitmq/erlang/search?distel%2F7https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-23.3.4.11-1.el7.x86_64.rpm?distro_ver…

Mysql | Error Code: 1153 - Got a packet bigger than ‘max_allowed_packet‘ bytes

描述 在执行sql语句插入的时候&#xff0c;报出了Error Code: 1153 - Got a packet bigger than ‘max_allowed_packet’ bytes 错误 解决方法 &#x1f6a9; 临时调整&#xff0c;重启后失效 1️⃣ 查询默认的max_allowed_packet值大小 执行语句&#xff1a; SHOW VARIABLE…

[Android Studio] Android Studio Virtual Device虚拟机的功能试用

&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Android Debug&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Topic 发布安卓学习过程中遇到问题解决过程&#xff0c;希望我的解决方案可以对小伙伴们有帮助。 &#x1f680;write…

JS:构造函数和原型

目录 1.构造函数和原型 1.1 概述 1.2 构造函数 1.2.1创建 1.2.2 静态成员和实例成员 1.3 构造函数原型 prototype 1.4 对象原型_proto_ 1.5 constructor 构造函数 1.6 构造函数、实例、原型对象的关系 1.7 原型链 2. 继承 2.1 call() 1.构造函数和原型 1.1 概述 在…

OpenStack

名词解释RAM 身份验证管理SDN 软件定义网络虚拟化为什么openstack清一色的KVM&#xff1f;KVM属于redhat&#xff0c;开源可定制nentron实现SDNSDN实现了东西向流量管理&#xff08;VPC与VPC的流量管理&#xff09;SDNVXLan实现隧道网络&#xff08;对等连接&#xff09;SDN在公…

【郭东白架构课 模块一:生存法则】04|法则二:架构师为什么要学习马斯洛的需求理论?

你好&#xff0c;我是郭东白&#xff0c;今天我们来聊聊架构师的第二个生存法则&#xff1a;架构活动需要尊重和顺应人性。 自从学习计算机专业的那一天起&#xff0c;我们似乎就走入了一个简单直接的机器世界&#xff0c;一个完全靠逻辑和数字主宰的世界。于是我们总不自觉地认…

如何将数据库结构导入到word

在navicat执行查询语句 SELECT COLUMN_NAME 备注, COLUMN_COMMENT 名称, COLUMN_TYPE 数据类型, false as 是键 FROM INFORMATION_SCHEMA.COLUMNS where -- wx 为数据库名称&#xff0c;到时候只需要修改成你要导出表结构的数据库即可 table_schema yuncourt_ai AND -- articl…

蓝库云|制造业数字化转型为何转不动?资料处理很重要

数字化转型已经成为每个产业势在必行的课题&#xff0c;没有人会怀疑数字化技术与科技能解放的生产力能量&#xff0c;但为什么看似美好的愿景&#xff0c;实行起来却如此缓慢&#xff1f;蓝库云认为这是因为没有盖好「资料治理」的地基。 面对不断变化的法令规范要求&#xf…

<<Java开发环境配置>>7-Apache Tomcat安装教程环境变量配置IDEA配置

一.Apache Tomcat简介: Apache是普通服务器&#xff0c;本身只支持html即普通网页。不仅可以通过插件支持php,还可以与Tomcat连通(单向Apache连接Tomcat,就是说通过Apache可以访问Tomcat资源。反之不然)。Apache只支持静态网页&#xff0c;但像php,cgi,jsp等动态网页就需要Tomc…