算法通过村第八关-树(深度优先)青铜笔记|经典算法题目

news2025/1/18 6:43:03

文章目录

  • 前言
  • 1. 二叉树里面的双指针
    • 1.1 判断两棵树是否相同
    • 1.2 对称二叉树
    • 1.3 合并二叉树
  • 2. 路径专题
    • 2.1 二叉树的所有路径
    • 2.2 路径总和
  • 3. 翻转的妙用
  • 总结


前言


提示:人类的底里是悲伤,我们都在用厚重的颜料,覆盖那些粗糙的线稿。--张皓宸《抬头看二十九次月亮》

前面的练习才是开始,这理才是真正的进入算法的门槛,来迎接下一波挑战吧。

1. 二叉树里面的双指针

所谓的双指针就是定义了两个变量,在二叉树中有时候也需要至少定义两个变量才能解决问题,这两个指针可能针对一颗树,也可能针对两棵树,我们这里就称他为”双指针“吧。这些问题一般是关于对称、翻转、合并等类型相关,我们接下来就看一些高频出现的问题吧。

1.1 判断两棵树是否相同

参考题目介绍:100. 相同的树 - 力扣(LeetCode)
在这里插入图片描述
在这里插入图片描述
这个貌似很好就容易实现了,两个二叉树同时进行前序遍历,先判断根节点是否相同,如果相同再分别判断左右子节点是否相同,判断的过程中只要存在一个不相同的就返回false,如果全部相同就返回true。其实也就是这样的。

	/**
     * 判断两个二叉树是否相同
     * @param p
     * @param q
     * @return
     */
    public static boolean isSameTree(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;
        }
        	// 走到这一步 说明p和q的节点是完全相同的,然后接着遍历
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }

这里你也可以试一试广度优先,这样写会怎么样,感兴趣可以试试。

1.2 对称二叉树

参考题目介绍:101. 对称二叉树 - 力扣(LeetCode)
在这里插入图片描述
在这里插入图片描述
如果树是镜像的,看看这个图,更加清晰:

在这里插入图片描述
因为我们要通过递归函数的返回值来判断这两个子树内测节点是否相同,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。这里的关键还是比较和如何处理结束的条件。单层递归的逻辑就是处理左右节点不为空且数值相同的情况。

  1. 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子
  2. 比较二叉树内侧是否对称:传入左节点的右孩子,右节点的左孩子
  3. 如果左右都对称就返回true,有一侧不对称就返回false。

接下来就是合并和简化代码的过程了:

	/**
     * 判断是否是一颗对称的二叉树
     * @param root
     * @return
     */
    public static boolean isSymmetric(TreeNode root) {
        // 校验参数
        if (root == null){
            return  true;
        }
        return check(root.left,root.right);
    }

    private static boolean check(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 check(p.left, q.right) && check(p.right, q.left);
    }

1.3 合并二叉树

参考题目介绍:617. 合并二叉树 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述
两个二叉树的对应节点可能存在一下三种情况,对于每种情况的使用不同的合并方式。

  1. 如果两个二叉树的对应节点都为空,则合并的二叉树的对应节点也为空;
  2. 如果两个二叉树的对应节点只有一个为空空,则合并后的二叉树的对应节点为其中的非空节点;
  3. 如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应值之和,此时需要显性合并两个节点。

对一个节点进行合并之后,还要对该节点的左右子树分别进行合并;

代码如下:

	/**
     * 合并两个二叉树
     *
     * @param t1
     * @param t2
     * @return
     */
    public static TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        // 校验参数
        if (t1 == null){
            return t2;
        }
        if (t2 == null){
            return t1;
        }
        // 建立一个新的树root
        TreeNode  merged = new TreeNode(t1.val + t2.val);
        merged.left = mergeTrees(t1.left,t2.left);
        merged.right = mergeTrees(t1.right,t2.right);
        return merged;
    }

如果这到题目没有想明白的话,就带入例子看看验证一下。

看了这么多,我们是不是也可以造一个提来看看:前面我们研究了两颗树相等和一棵树对称的情况,我们这里可以造一个判断题,怎么证明两棵树是否对称。想一下这个要怎么写💡
在这里插入图片描述

2. 路径专题

关于二叉树有些题目与路径有关,我们这里好好看下。回溯这个看起来头疼的问题,我们先放在后面,看看这个路径专题哈哈🥰

2.1 二叉树的所有路径

参考题目介绍:257. 二叉树的所有路径 - 力扣(LeetCode)
在这里插入图片描述
在这里插入图片描述

我们可以注意有几个叶子节点,有几个就说明有几条路径,这么问题就转换成了怎么找叶子节点了。我们知道深度优先搜索就是从根节点出发一直寻到叶子节点,这里我们可以先判断当前节点是不是叶子节点,再决定是不是向下走,如果是叶子节点,我们就增加一条路径,就像下图一样。
在这里插入图片描述
这里还有个问题,当得到一个叶子节点容易,这时候怎么知道它所在的完整路径是什么呢?例如上图中的D之后,怎么知道前面还有A和C呢?这里简单,我们增加一个String类型的变量,访问每个节点的时候先将他存入String中,到叶子节点的时候再添加到集合中:

具体代码如下:

	/**
     * 二叉树的所有路径
     * @param root
     * @return
     */
    public static List<String> binaryTreePaths(TreeNode root) {
        // 创建空间
        List<String> res = new ArrayList<String>();
        // 广度优先搜索
        dfs(root, "", res);
        return res;
    }

    private static void dfs(TreeNode root, String path, List<String> res) {
        // 终止条件
        if (root == null) {
            return;
        }
        // 到达叶子节点
        if (root.left == null && root.right == null) {
            res.add(path + root.val);
            return;
        }
        // 分别遍历左右子树
        dfs(root.left, path + root.val + "->", res);
        dfs(root.right, path + root.val + "->", res);
    }

这个题目是回溯的基础入门问题,我们后面再讲,感兴趣的同学可以带入例子看看。

2.2 路径总和

参考题目介绍:112. 路径总和 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述
本题目询问是否存在从当前节点root到叶子节点的路径,满足其路径和为sun,假定从根节点到当前节点的值之和为val,我们可以将这个大问题转换成为一个小问题:是否存在从当前节点的子节点到叶子的路径,其满足路径和为sum - val。

不难看出这个问题满足递归的性质,若当前节点就是叶子节点,那么我们直接判断sum是否等于val即可(因为路径和已经确定,就是当前节点的值,我们只要判断该路径和是否满足条件)。若当前节点不是叶子节点,需要继续递归询问它的子节点是否满足条件。

代码就好写多了😎:

	/**
     *  路径总和
     *
     * @param root
     * @param sum
     * @return
     */
    public static boolean hasPathSum(TreeNode root, int sum) {
        // 终止条件
        if(root == null){
            return false;
        }
        if (root.left == null && root.right == null){
            return sum == root.val;
        }
        // 接着遍历左右子树
        boolean left = hasPathSum(root.left,sum - root.val);
        boolean right = hasPathSum(root.right,sum - root.val);
        return left || right;
    }

学了这道题,感性取的话可以挑战一下路径总和II。这里必考虑回溯问题

推荐题目⭐⭐⭐⭐

113. 路径总和 II - 力扣(LeetCode)

3. 翻转的妙用

参考题目介绍:226. 翻转二叉树 - 力扣(LeetCode)

在这里插入图片描述
这个题目同类型题:剑指 Offer 27. 二叉树的镜像 - 力扣(LeetCode)简直是一摸一样。当然了,根据上图,我们可以发现想要翻转树,就是把每个节点的左右孩子交换一下。关键在于遍历顺序,前中后你觉的那个更适合呢?遍历的过程中取翻转每一个节点的左右孩子就可以达到整体翻转的效果。注意只要把每一个节点的左右孩子翻转一下就可以达到整体翻转的效果。

这是一道很经典的二叉树问题。显然,我们从根节点开始,递归对树进行遍历,并从叶子节点先开始翻转。如果当前遍历到的节点root的左右两颗子树都已经翻转过,那么我们只需要交换两个子树的位置,就可以完成以root为根节点的整棵树的翻转。

话不多说先看看前序交换怎么实现的,代码如下:

	/**
     * 前序遍历的二叉树翻转
     * @param root
     * @return
     */
    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;
        
    }

那么后序遍历呢?

	/**
     * 后序遍历的二叉树翻转
     * @param root
     * @return
     */
    public TreeNode invertTree(TreeNode root) {
        // 终止条件
        if (root == null) {
            return null;
        }
        // 先左右
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        return root;

    }

这道题目使用前序和后序遍历都可以,你猜对了吗?💕主要区别就是,前序是先处理子节点,属于自顶向下,后序是先处理叶子节点最后处理自己,属于自下而上的。我们来看这个图:
在这里插入图片描述
当然了,本题还可以使用层序遍历实现,核心思想就是元素出队时,先将其左右孩子不直接入队,而是反转后再放进去,顺便也看下代码吧:

	/**
     * 层序遍历的二叉树翻转
     *
     * @param root
     * @return
     */
    public TreeNode invertTree(TreeNode root) {
        // 参数校验
        if (root == null) {
            return null;
        }
        // 创建空间
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        // 根节点入队列
        queue.offer(root);
        // 只要队列不为空 就一直遍历
        while(!queue.isEmpty()){
            // 每次从队列中拿到一个节点,并交换这个节点的左右子树
            TreeNode temp = queue.poll();
            TreeNode left = temp.left;
            temp.left = temp.right;
            temp.right = left;
            // 如果当前节点的左子树不空  放入队列等待处理
            if (temp.left != null){
                queue.offer(temp.left);
            }
            // 如果当前节点的右子树不空  放入队列等待处理
            if (temp.right != null){
                queue.offer(temp.right);
            }
        }
        return root;
    }

总结

提示:二叉树的双指针问题;路径问题;翻转问题;回溯初始

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

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

相关文章

Vulnhub实战-prime1

前言 VulnHub 是一个面向信息安全爱好者和专业人士的虚拟机&#xff08;VM&#xff09;漏洞测试平台。它提供了一系列特制的漏洞测试虚拟机镜像&#xff0c;供用户通过攻击和漏洞利用的练习来提升自己的安全技能。本次&#xff0c;我们本次测试的是prime1。 一、主机发现和端…

Verdi实现信号的平移

在Verilog/System verilog中&#xff0c;# xxx可以实现延迟指定时间的功能&#xff0c;而在使用verdi查看信号波形并进行分析时&#xff0c;同样也可以实现类似的功能。 (注&#xff1a;这种信号平移是有其应用场景的&#xff0c;例如&#xff0c;在某些仿真模型中&#xff0c;…

Vue2电商前台项目——完成加入购物车功能和购物车页面

Vue2电商前台项目——完成加入购物车功能和购物车页面 文章目录 Vue2电商前台项目——完成加入购物车功能和购物车页面一、加入购物车1、路由跳转前先发请求把商品数据给服务器&#xff08;1&#xff09;观察接口文档&#xff08;2&#xff09;写接口&#xff08;3&#xff09;…

知识深度 VS 知识广度

知识深度&#xff1a;帮助更快的朝着目标进。 发知识广度&#xff1a;帮助找到最优的路径。 职业生涯的前期需要执行力&#xff0c;因此需要更多的锻炼知识深度。越往后需要更多的做决策&#xff0c;因此要更多提升知识广度。

git 远程名称 远程分支 介绍

原文&#xff1a; 开发者社区> 越前君> 细读 Git | 让你弄懂 origin、HEAD、FETCH_HEAD 相关内容 读书笔记&#xff1a;担心大佬文章搬家&#xff0c;故整理此学习笔记 远程名称&#xff08;Remote Name&#xff09; Origin 1、 origin 只是远程仓库的一个名称&#xff…

代码管理工具git1

ctrl 加滚轮 放大字体 在计算机任意位置单击右键&#xff0c;选择&#xff1a;&#xff1a;Git Bash Here git version git清屏命令&#xff1a;ctrl L查看用户名和邮箱地址&#xff1a; $ git config user.name$ git config user.email修改用户名和邮箱地址&#xff1a;$ git…

防止泄露,保护隐私!如何清除你的谷歌搜索历史记录

按照以下说明学习如何从你的谷歌帐户、谷歌Chrome浏览器、谷歌iOS或Android应用程序或谷歌应用程序中删除你的谷歌历史记录。 如何从你的谷歌帐户中删除搜索历史记录 清除你的谷歌搜索历史并不意味着谷歌实际上会删除你的搜索数据。谷歌仍然会记录你如何以及何时使用某些功能…

数据结构——二叉树提升

二叉树题型练习 前言一、节点个数以及高度等二、二叉树OJ题二叉树的前序遍历二叉树的中序遍历二叉树的后序遍历单值二叉树二叉树最大深度检查两颗树是否相同.翻转二叉树对称二叉树另一颗树的子树 总结 前言 现在我们开始一轮新的自我提升吧&#xff01; 二叉树的题目当然也更有…

【每日一题】34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09; 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 […

【LeetCode每日一题合集】2023.9.11-2023.9.17(⭐反悔贪心拓扑排序Floyd)

文章目录 630. 课程表 III解法——反悔贪心⭐⭐⭐⭐⭐ 1462. 课程表 IV⭐解法1——拓扑排序预处理解法2——Floyd算法判断是否存在路径 2596. 检查骑士巡视方案&#xff08;方向模拟&#xff09;1222. 可以攻击国王的皇后&#xff08;方向模拟&#xff09;LCP 50. 宝石补给&…

Java学习之常见易错点总结--第一期

&#x1f495;"不要同情自己&#xff0c;那是卑劣懦夫干的勾当。"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;Java学习之常见易错点总结--第一期 1.什么时候变量不用初始化&#xff1f; 先来看如下代码&#xff1a; public static void main(…

MediaPipe+OpenCV 实现实时手势识别(附Python源码)

MediaPipe官网&#xff1a;https://developers.google.com/mediapipe MediaPipe仓库&#xff1a;https://github.com/google/mediapipe 一、MediaPipe介绍 MediaPipe 是一个由 Google 开发的开源跨平台机器学习框架&#xff0c;用于构建视觉和感知应用程序。它提供了一系列预训…

C#中Visual Studio如何为解决方案设置启动项目

目录 第一种方法:快速选定启动项目的方法1.在解决方案资源管理器中,选择解决方案(最高层节点)2.选择解决方案节点的上下文(右键单击)菜单,然后选择“属性”。 “解决方案属性页”对话框随即显示第二种方法:右击First11或者second11,点击设置启动项目即可Visual Studio…

C++ PrimerPlus 复习 第七章 函数——C++的编程模块(上)

第一章 命令编译链接文件 make文件 第二章 进入c 第三章 处理数据 第四章 复合类型 &#xff08;上&#xff09; 第四章 复合类型 &#xff08;下&#xff09; 第五章 循环和关系表达式 第六章 分支语句和逻辑运算符 第七章 函数——C的编程模块&#xff08;上&#xff…

【红包雨】中间件与环境安装

创建环境 创建专用网络VPC 安全组 创建云服务器 打包部署 2. Java环境 #下载jdk17 wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz #安装上传工具 以后使用命令 rz 选中文件进行上传 yum install -y lrzsz#解压 tar -xzvf jdk-17_linux-x64…

Hive参数与性能调优-V2.0

Hive作为大数据平台举足轻重的框架&#xff0c;以其稳定性和简单易用性也成为当前构建企业级数据仓库时使用最多的框架之一。 但是如果我们只局限于会使用Hive&#xff0c;而不考虑性能问题&#xff0c;就难搭建出一个完美的数仓&#xff0c;所以Hive性能调优是我们大数据从业…

VMware Fusion 13+Ubuntu ARM Server 22.04.3在M2芯片的Mac上共享文件夹

因为Server版没有桌面&#xff0c;VMware Tools不能直接装&#xff0c;导致没办法共享文件。 Ubuntu中的包如果需要更新&#xff0c;先执行下面的步骤 sudo apt update 再执行 sudo apt upgrade 不需要更新的话&#xff0c;直接执行下面的步骤 先把open-vm-tools卸载了 …

【JavaSE笔记】抽象类与接口

一、抽象类 1、概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 package demo2…

React 开发一个移动端项目(2)

配置基础路由 目标&#xff1a;配置登录页面的路由并显示在页面中 步骤&#xff1a; 安装路由&#xff1a; yarn add react-router-dom5.3.0 5 和 6 两个版本对组件类型的兼容性和函数组件支持有所改变&#xff0c;在这里使用的是 5。 和路由的类型声明文件 yarn add types…

AI AIgents时代-(三.)AutoGPT和AgentGPT

前两篇讲解了Agent的原理和组件&#xff0c;这节我将给大家介绍两个agent项目&#xff0c;给出它们的工作原理和区别&#xff0c;并教大家亲手尝试使用 Agents&#x1f389; &#x1f7e2; AutoGPT&#x1f916;️ 我们的老朋友&#xff0c;之前文章也专门写过。AutoGPT 是一…