【算法思想·二叉树】后续篇

news2024/9/20 5:36:16

本文参考labuladong算法笔记[二叉树心法(后序篇 | labuladong 的算法笔记]

前序位置的代码只能从函数参数中获取父节点传递来的数据,而后序位置的代码不仅可以获取参数数据,还可以获取到子树通过函数返回值传递回来的数据。

那么换句话说,一旦你发现题目和子树有关,那大概率要给函数设置合理的定义和返回值,在后序位置写代码了

652. 寻找重复的子树 | 力扣  | LeetCode  |

给你一棵二叉树的根节点 root ,返回所有 重复的子树 

对于同一类的重复子树,你只需要返回其中任意 一棵 的根结点即可。

如果两棵树具有 相同的结构 和 相同的结点值 ,则认为二者是 重复 的。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 树中的结点数在 [1, 5000] 范围内。
  • -200 <= Node.val <= 200
// 函数签名如下
List<TreeNode> findDuplicateSubtrees(TreeNode root);

我来简单解释下题目,输入是一棵二叉树的根节点 root,返回的是一个列表,里面装着若干个二叉树节点,这些节点对应的子树在原二叉树中是存在重复的。

说起来比较绕,举例来说,比如输入如下的二叉树:

首先,节点 4 本身可以作为一棵子树,且二叉树中有多个节点 4:

类似的,还存在两棵以 2 为根的重复子树:

那么,我们返回的 List 中就应该有两个 TreeNode,值分别为 4 和 2(具体是哪个节点都无所谓)。

【思路】

这题咋做呢?还是老套路,先思考,对于某一个节点,它应该做什么

比如说,你站在图中这个节点 2 上:

如果你想知道以自己为根的子树是不是重复的,是否应该被加入结果列表中,你需要知道什么信息?

你需要知道以下两点

1、以我为根的这棵二叉树(子树)长啥样

2、以其他节点为根的子树都长啥样

这就叫知己知彼嘛,我得知道自己长啥样,还得知道别人长啥样,然后才能知道有没有人跟我重复,对不对?好,那我们一个一个来看。

首先来思考,我如何才能知道以自己为根的这棵二叉树长啥样

其实想到这里,就可以判断本题需要在二叉树的后序位置写代码了。

为什么?很简单呀,我要知道以自己为根的子树长啥样,是不是得先知道我的左右子树长啥样,再加上自己,就构成了整棵子树的样子?左右子树的样子,可不就得在后序位置通过递归函数的返回值传递回来吗?

如果你还绕不过来,我再来举个非常简单的例子:计算一棵二叉树有多少个节点。这个代码应该会写吧:

def count(root):
    if root == None:
        return 0
    # 先算出左右子树有多少节点
    left = count(root.left)
    right = count(root.right)
    # 后序位置,子树加上自己,就是整棵二叉树的节点数
    res = left + right + 1
    return res

这不就是标准的后序遍历框架嘛,和我们本题在本质上没啥区别对吧。

现在,明确了要用后序遍历,那应该怎么描述一棵二叉树的模样呢?我们后文 序列化和反序列化二叉树 其实写过了,二叉树的前序/中序/后序/层序遍历结果可以描述二叉树的结构。

那么,我就以后序遍历结果作为序列化结果吧,可以通过拼接字符串的方式把二叉树序列化,看下代码:

# 定义:输入以 root 为根的二叉树,返回这棵树的序列化字符串
def serialize(root):
    # 对于空节点,可以用一个特殊字符表示
    if root is None:
        return "#"
    # 将左右子树序列化成字符串
    left = serialize(root.left)
    right = serialize(root.right)
    # 后序遍历代码位置
    # 左右子树加上自己,就是以自己为根的二叉树序列化结果
    myself = f"{left},{right},{root.val}"
    return myself

我们用非数字的特殊符 # 表示空指针,并且用字符 , 分隔每个二叉树节点值,这属于序列化二叉树的套路了,不多说。

注意我们 myself 是按照左子树、右子树、根节点这样的顺序拼接字符串,也就是后序遍历顺序。因为我们这里的目的是通过序列化唯一描述一棵二叉树的结构,所以你也可以用前序顺序来拼接字符串,但是注意不能用中序顺序,具体原因参见后文 序列化和反序列化二叉树 的总结。

这样,我们第一个问题就解决了,对于每个节点,递归函数中的 myself 变量就可以描述以该节点为根的二叉树。

现在我们解决第二个问题,我知道了自己长啥样,怎么知道别人长啥样?这样我才能知道有没有其他子树跟我重复对吧。

这很简单呀,我们借助一个外部数据结构,让每个节点把自己子树的序列化结果存进去,这样,对于每个节点,不就可以知道有没有其他节点的子树和自己重复了么?

初步思路可以使用 HashSet 记录所有子树的序列化结果,代码如下:

class Solution:
    # 记录所有子树
    subTrees = set()
    # 记录重复的子树根节点
    res = []

    def serialize(self, root):
        if root == None:
            return "#"

        # 左右子树的序列化结果
        left = self.serialize(root.left)
        right = self.serialize(root.right)

        # 后序位置,计算以自己为根的二叉树序列化结果
        myself = left + "," + right + "," + str(root.val)
        if myself in self.subTrees:
            # 有人和我重复,把自己加入结果列表
            self.res.append(root)
        else:
            # 暂时没人跟我重复,把自己加入子树集合
            self.subTrees.add(myself)
        return myself

但是呢,这有个问题,如果出现多棵重复的子树,结果集 res 中必然出现重复,而题目要求不希望出现重复。

为了解决这个问题,可以把 HashSet 升级成 HashMap,额外记录每棵子树的出现次数:

【python】

class Solution:
    def __init__(self):
        # 记录所有子树以及出现的次数
        self.memo = {}
        # 记录重复的子树根节点
        self.res = []

    # 主函数
    def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]:
        self.serialize(root)
        return self.res

    def serialize(self, root):
        if root is None:
            return "#"

        left = self.serialize(root.left)
        right = self.serialize(root.right)

        subTree = left + "," + right + "," + str(root.val)

        freq = self.memo.get(subTree, 0)
        # 多次重复也只会被加入结果集一次
        if freq == 1:
            self.res.append(root)
        # 给子树对应的出现次数加一
        self.memo[subTree] = freq + 1
        return subTree

总结

1、需要比较每一颗子树,要如何遍历?——后序遍历。

2、如何比较每一颗子树的结构和值?——序列化的方式。

3、遇到多个重复子树怎么办?——用字典来存放每个子树出现的次数。

4、重视函数签名——参数是什么?返回值又是什么?

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

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

相关文章

软件设计师备考——计算机系统

学习内容源自「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 目录 1.1.1 计算机系统硬件基本组成 1.1.2 中央处理单元 1.CPU 的功能 1)运算器 2)控制器 RISC && CISC 流水线控制 存储器 Cache 中断 输入输出IO控制方式 程序查询方式 中断驱动方…

在python里把图变成gif

import scipy.io import matplotlib.pyplot as plt import imageio import numpy as npdata scipy.io.loadmat("/文件路径/Sol.mat")# 提取数据 t_current data[t].flatten() XX, YY np.meshgrid(data[x].flatten(), data[y].flatten()) u_pred_plot_final data[…

轻松应对:环保专包二级资质续期常见问题解答

在环保专包二级资质续期过程中&#xff0c;企业可能会遇到一系列常见问题。以下是对这些问题的详细解答&#xff0c;旨在帮助企业轻松应对续期挑战&#xff1a; 一、政策理解与把握 问题&#xff1a;如何准确理解和把握最新的环保政策和资质续期要求&#xff1f; 解答&#x…

HCIA--实验十三:VLAN间通信子接口实验/双单臂路由实验

一、实验内容 1.需求/要求&#xff1a; 将两个单臂路由通过两台交换机连接起来&#xff0c;成为双臂路由&#xff0c;并探讨这么做的原因。实现全网通&#xff0c;让任何一台主机之间都可以通信。 二、实验过程 1.拓扑图&#xff1a; 2.步骤&#xff1a; 1.给PC配置ip地址…

传感器技术在构建实时监控系统中有什么作用

在无线传感器技术中&#xff0c;物联网生成的传感器数据通过无线方式传输到网络服务器&#xff0c;工程师可以在其中跟踪参数。远距离无线通信提高了工业 4.0 的成本效率并减少了人力。实时监控系统旨在显示传感器节点周围的快速变化&#xff0c;这需要快速、低延迟的数据传输。…

数据分析与挖掘课程相关资源

这是在gitee上整的关于这门课的一个开源项目。 https://gitee.com/rainpet/python-data-analysis-and-mining-demo 头歌平台。 常见问题&#xff1a; 1、如何确认conda的版本&#xff0c;执行如下命令&#xff1a; conda list anaconda$2、实验室登陆后&#xff0c;无法上网&a…

JAVA开源项目 大学生租房平台 计算机毕业设计

本文项目编号 T 019 &#xff0c;文末自助获取源码 \color{red}{T019&#xff0c;文末自助获取源码} T019&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

浅谈人工智能之基于ollama的常见变量设置

浅谈人工智能之基于ollama的常见变量设置 全局命令设置 现象&#xff1a;无法直接使用命令ollama 问题显示如下&#xff1a; [rootlocalhost LLM]# ollama -bash: ollama: command not found 解决方法一&#xff1a; 第一步&#xff1a;输入如下命令&#xff1a; [rootloca…

Android Studio偶尔打开Flutter项目没有智能提示的解决方案

Flutter支持多种IDE来编程&#xff0c;我曾使用过Android Studio和VSC两款软件&#xff0c;但因为长期使用Android Studio的原因&#xff0c;使用起来会比VSC顺手&#xff0c;然后就发现偶尔AS加载Flutter项目会无法使用智能提示&#xff0c;也没有代码高亮等 问题出现的原因&…

力扣面试150 三角形最小路径和 DFS 记忆化搜索 DP 滚动数组优化DP

Problem: 120. 三角形最小路径和 &#x1f469;‍&#x1f3eb; 甜姐题解 Code class Solution {// 朴素DP// public int minimumTotal(List<List<Integer>> triangle) {// int n triangle.size();// int[][] dp new int[n1][n1];// for(int i n-1; i > 0…

公积金基数两万,养了征信三个月之后,结果怎么样了?

近期&#xff0c;有幸与一位颇具代表性的信贷经历者&#xff0c;进行了深入的交流。她的故事&#xff0c;尤其是对那些在信贷领域迷茫徘徊的朋友来说&#xff0c;无疑是一盏警示灯&#xff0c;提醒我们如何在金融海洋中稳健航行。 黄女士的信贷迷航 黄女士&#xff0c;一位拥有…

SpringMvc 之处理器方法参数解析器(HandlerMethodArgumentResolver)

概述 HandlerMethodArgumentResolver 是 Spring MVC 框架中的一个关键组件&#xff0c;用于解析控制器&#xff08;Controller&#xff09;方法的参数。在 Spring MVC 中&#xff0c;当一个请求到达时&#xff0c;DispatcherServlet 会负责找到对应的处理器&#xff08;即控制器…

9月9日星期一今日早报简报微语报早读

9月9日星期一&#xff0c;农历八月初七&#xff0c;早报微语早读。 1、庆祝第40个教师节&#xff0c;全国585个单位、1790人受表彰&#xff1b; 2、中国残奥军团94金76银50铜收官&#xff1a;连续6届残奥会金牌和奖牌榜第一&#xff1b; 3、三部门&#xff1a;拟允许在京津沪…

1688电商运营到底怎么做竞品分析(超细节)

你得学会看同行数据&#xff0c;因为同行是蕞好的老师。你把同行分析透&#xff0c;把市场分析透以后&#xff0c;你才能真正的做好这个类目。我们就来详细的讲一下具体该怎么去分析同行&#xff0c;以及要看竞品的哪些数据。 一、分析市场 就是先看一下你的这个产品&#xf…

【go-zero】api与rpc使用k8s服务发现和部署

【go-zero】api与rpc使用k8s服务发现和部署 k8s安装 参考 debian12极简快速安装k8s 1、代码准备 参考上一篇【go-zero】api与rpc使用etcd服务发现 代码搬过来&#xff0c;因为要用到k8s环境换到linux了 &#xff0c;做一下修改 rpc 修改一下rpc的logic文件&#xff0c;增…

运放双电源供电和单电源供电的区别

运放是一种常见的电路元器件&#xff0c;广泛应用于模拟电路中。它可以使用不同的电源电压进行供电&#xff0c;其中最常见的是双电源供电和单电源供电。 下面将详细介绍它们之间的区别。 1.双电源供电 双电源供电意味着运放有两个电源引脚&#xff1a;正极和负极。正极一般连…

docker部署it工具箱,各种工具包

1.效果 2.步骤 2.1安装docker&&docker-compose 复制粘贴两脚本 bash <(curl -sSL https://cdn.jsdelivr.net/gh/SuperManito/LinuxMirrorsmain/DockerInstallation.sh)curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose…

调度任务是什么?如何设置调度任务?

本文将解释什么是调度任务&#xff0c;并且以FineDataLink为例说明如何设置调度任务。 一. 什么是调度任务&#xff1f; 调度平台是管理和自动化 ETL 的任务执行工具&#xff0c;通过指定任务依赖关系和执行顺序实现任务的自动执行&#xff0c;一般需要代码开发&#xff0c;使…

一些面试和找工作的技巧-新资要的低并不会给你加分薪资要的高不会成为公司拒绝你的核心理由

面试相关 自我介绍的模板 使用STAR描述->描述项目介绍 核心岗位是内推&#xff0c;或者朋友 所以给同事留个好印象 面试最后一道送命题目 你还有什么想问的吗? 这个问题体现了你深度思考的能力&#xff0c;对 一轮面试官可能是你的直属领导&#xff0c;你可以问题你进…

GIS大事件!Bentley收购Cesium

9月6日&#xff0c;Cesium官方宣布加入Bentley。 Bentley我们并不陌生。最初Acute3D被Bentley公司收购&#xff0c;旗下软件由Smart3DCapture转型到ContextCapture&#xff0c;现又改名 iTwin Capture。 如今又收购了Cesium。 Cesium官方表示&#xff0c;Cesium开发平台与iTwi…