学算法认准 labuladong

后台回复进群一起力扣????

读完本文,你可以去力扣解决:

652.寻找重复子树(Medium

接前文 手把手带你刷二叉树(第一期)和 手把手带你刷二叉树(第二期),本文继续来刷二叉树。

从前两篇文章的阅读量来看,大家还是能够通过二叉树学习到 框架思维 的。但还是有不少读者有一些问题,比如如何判断我们应该用前序还是中序还是后序遍历的框架

那么本文就针对这个问题,不贪多,给你掰开揉碎只讲一道题。

还是那句话,根据题意,思考一个二叉树节点需要做什么,到底用什么遍历顺序就清楚了

看题,这是力扣第 652 题「寻找重复子树」:

函数签名如下:

List<TreeNode> findDuplicateSubtrees(TreeNode root);

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

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

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

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

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

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

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

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

你需要知道以下两点

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

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

这就叫知己知彼嘛,我得知道自己长啥样,还得知道别人长啥样,然后才能知道有没有人跟我重复,对不对?

好,那我们一个一个来解决,先来思考,我如何才能知道以自己为根的二叉树长啥样

其实看到这个问题,就可以判断本题要使用「后序遍历」框架来解决:

void traverse(TreeNode root) {traverse(root.left);traverse(root.right);/* 解法代码的位置 */
}

为什么?很简单呀,我要知道以自己为根的子树长啥样,是不是得先知道我的左右子树长啥样,再加上自己,就构成了整棵子树的样子?

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

int count(TreeNode root) {if (root == null) {return 0;}// 先算出左右子树有多少节点int left = count(root.left);int right = count(root.right);/* 后序遍历代码位置 */// 加上自己,就是整棵二叉树的节点数int res = left + right + 1;return res;
}

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

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

所以,我们可以通过拼接字符串的方式把二叉树序列化,看下代码:

String traverse(TreeNode root) {// 对于空节点,可以用一个特殊字符表示if (root == null) {return "#";}// 将左右子树序列化成字符串String left = traverse(root.left);String right = traverse(root.right);/* 后序遍历代码位置 */// 左右子树加上自己,就是以自己为根的二叉树序列化结果String subTree = left + "," + right + "," + root.val;return subTree;
}

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

注意我们subTree是按照左子树、右子树、根节点这样的顺序拼接字符串,也就是后序遍历顺序。你完全可以按照前序或者中序的顺序拼接字符串,因为这里只是为了描述一棵二叉树的样子,什么顺序不重要。

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

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

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

初步思路可以使用HashSet记录子树,代码如下:

// 记录所有子树
HashSet<String> memo = new HashSet<>();
// 记录重复的子树根节点
LinkedList<TreeNode> res = new LinkedList<>();String traverse(TreeNode root) {if (root == null) {return "#";}String left = traverse(root.left);String right = traverse(root.right);String subTree = left + "," + right+ "," + root.val;if (memo.contains(subTree)) {// 有人和我重复,把自己加入结果列表res.add(root);} else {// 暂时没人跟我重复,把自己加入集合memo.add(subTree);}return subTree;
}

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

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

// 记录所有子树以及出现的次数
HashMap<String, Integer> memo = new HashMap<>();
// 记录重复的子树根节点
LinkedList<TreeNode> res = new LinkedList<>();/* 主函数 */
List<TreeNode> findDuplicateSubtrees(TreeNode root) {traverse(root);return res;
}/* 辅助函数 */
String traverse(TreeNode root) {if (root == null) {return "#";}String left = traverse(root.left);String right = traverse(root.right);String subTree = left + "," + right+ "," + root.val;int freq = memo.getOrDefault(subTree, 0);// 多次重复也只会被加入结果集一次if (freq == 1) {res.add(root);}// 给子树对应的出现次数加一memo.put(subTree, freq + 1);return subTree;
}

这样,这道题就完全解决了,题目本身算不上难,但是思路拆解下来还是挺有启发性的吧?

往期推荐 ????

双指针技巧秒杀四道数组/链表题目

给我 O(1) 时间,我能查找/删除数组中的任意元素

手把手解决三道括号相关的算法题

一文秒杀所有区间相关问题

一道找中位数的算法题把东哥整不会了…

_____________

学好算法靠套路,认准 labuladong,知乎、B站账号同名。

《labuladong的算法小抄》即将出版,公众号后台回复关键词「pdf」下载,回复「进群」可加入刷题群。

东哥手把手带你刷二叉树|第三期相关推荐

  1. 东哥手把手帮你刷通二叉树|第二期

    学算法认准 labuladong 东哥带你手把手撕力扣???? 读完本文,你能去力扣解决如下题目: 654.最大二叉树(难度 Medium) 105.从前序与中序遍历序列构造二叉树(难度 Medium ...

  2. 东哥手把手带你套框架刷通二叉树|第一期

    学算法认准 labuladong 东哥带你手把手撕力扣???? 点击下方卡片即可搜索???? 读完本文,你可以去力扣拿下: 226. 翻转二叉树,难度 Easy 114. 将二叉树展开为链表,难度 M ...

  3. 鹏哥手把手带我刷好题 · 编程练习 · I

    大家好,我是安然无虞.  目录 在线OJ <1>.什么是在线OJ <2>.为什么训练在线OJ 1.实践出真知 2.我是大V 3.有容乃大 4.缩短二进制 5.反向输出一个四位数 ...

  4. 鹏哥手把手带我刷好题 · 编程练习 · II

    大家好,我是安然无虞. 目录 1.判断字母 2.字符圣诞树 3.ASCII码 4.出生日期的输入输出 5.2的n次方计算 6.按照格式输入并交换输出 7.字符转ASCII码 8.计算表达式的值 9.计 ...

  5. 手把手带你刷Leetcode力扣 学习总结

    文章目录 1. 总体规划 2. 算法复杂度 2.1 时间复杂度 2.2 空间复杂度 3. 数据结构 3.1 数组[Array] 3.1.1 Python常用操作 3.1.2 Java常用操作 3.1. ...

  6. 【手把手带你刷Leetcode力扣】10.数据结构 -图

    图: 顶点 邻居节点 边 度:边的数量 无向图 有向图 入度:指向该顶点的边的数量 出度:以该顶点为起点指向别的顶点的边的数量 权重图 最短路径 贝尔曼-福特算法(Bellman-Ford) 迪克斯特 ...

  7. 手把手带你刷好题(牛客刷题⑤)

    作者:月亮嚼成星~ 博客主页:月亮嚼成星~的博客主页 专栏:手把手带你刷牛客 工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器--牛客网 点击免费注册和我一起刷题吧 1.类中的数据域使 ...

  8. 【手把手带你刷好题】—— 48.二叉树的层序遍历(BFS)

    [前言] 今天是刷题打卡第48天! 早成者未必有成,晚达者未必不达.一起都还来得及,加油哦. 原题: 二叉树的层序遍历(BFS) 原题链接:力扣 示例: 代码执行: class Solution { ...

  9. BCrypt加密怎么存入数据库_松哥手把手带你入门 Spring Security,别再问密码怎么解密了...

    因为之前有小伙伴在松哥群里讨论如何给微人事的密码解密,我看到聊天记录后就惊呆了. 无论如何我也得写一篇文章,带大家入门 Spring Security!当我们在一个项目中引入 Spring Secur ...

最新文章

  1. mysql多索引结构_MySQL 索引结构
  2. 中过滤记录中时间_水肥一体化中常见的过滤器
  3. Mysql练习题15-给定数字的频率查询中位数
  4. js 控制页面跳转的5种方法
  5. java 获取指定后缀名的文件
  6. pyinstaller 打包exe可执行文件
  7. 网上收集总结一下mssql( 部分)
  8. 2014年听写VOA50篇
  9. python 复杂数据相似度计算_Opencv python图像处理-图像相似度计算
  10. CAD迷你画图 for mac
  11. silverlight---游戏中的人工智能之追逐与闪躲
  12. VB.NET 教程_01_基础语法
  13. 解决:samba 无法访问,您可能没有权限使用网络资源,请与这台服务器管理员联系 指定的网络名不可用
  14. 联想笔记本G50-80 bios白名单修改
  15. java实现gdal栅格矢量化_gdal栅格矢量化 - osc_lfs4vsih的个人空间 - OSCHINA - 中文开源技术交流社区...
  16. Parallels Desktop 16在Big Sur下网络初始化失败解决办法
  17. 生产任务分配问题 matlab+lingo
  18. 计算机水平意见,【要闻】我省从明年起基层评职称对外语和计算机水平不作要求...
  19. P6与BIM,上海迪士尼BIM应用总结及P6软件应用经验
  20. 三层架构(UI、BLL、DAL)

热门文章

  1. Layaair DragonBones 龙骨动画的换皮肤
  2. 计算机的存储程序是谁提出来的,计算机储存程序和程序原理是谁提出来的
  3. CorelDRAWX4的VBA插件开发(三十四)调用C++实现一键智能群组(第3节)主调函数以及三个被调函数
  4. centos 7 jenkins一健发布asp.net项目
  5. adb隐藏状态栏图标,Android 完全隐藏状态栏方法
  6. 运维面试必问的负载均衡高频面试题(2021年最新版)
  7. telnetlib备份cisco交换机10054
  8. 【软件测试】白盒测试与黑盒测试
  9. Xshell7家庭版
  10. 理光Ricoh Aficio 550 一体机驱动