Tree_search
Tree Search
目录结构
队列的运用:一、二、
一、翻转树
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
| |
二、相同树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
| |
三、层序遍历并标序(自定义)(通解性)
此题为自定义的,具有通解性质,可暴力解决很多题目。
给你一个二叉树的根节点 root ,一层一层地打印并标明位置,包括空结点。
关键是通过上层序号,向左子树走+0,向右子树走+1
打印类似:
(root,0)
(left,0) (right,1)
(left_left,0) (left_right,1) (none,2) (right_right,3)
(?,0) (?,1) (?,2) (?,3) (?,6) (?,7)
| |
四、对称树
给你一个二叉树的根节点 root , 检查它是否轴对称。
| |
| |
五、二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
两种思路:自顶向下 和 自底向上
- 自顶向下:顶层为1,向下加一并取最大返回
| |
- 自底向上:最底空结点为0,一直向上加,太惊艳了
“我在网上看到一种理解递归的说法:「在写递归函数时,可以假设递归返回的结果一定是正确的」。其实这种说法本质上就是数学归纳法。”——作者
代码中 max(l_depth, r_depth) + 1 就是假设l_depth 和 r_depth是正确的,然后本层则为 max(l_depth, r_depth) + 1
if root is None: return 0 l_depth = self.maxDepth(root.left) r_depth = self.maxDepth(root.right) 则保证了初始状况也是正确的,刚才的假设便成立,就能得到正确结果。
| |
六、二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
| |
七、路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
| |
八、二叉树层序遍历无标序号
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
“三、层序遍历并标序”的简单版。
| |
九、岛屿的周长
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
| |
| |
十、左叶子之和
给定二叉树的根节点 root ,返回所有左叶子之和。
| |
| |
十一、二叉搜索树的最小绝对差
530. 二叉搜索树的最小绝对差 - 力扣(LeetCode)
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
What I learn:
- 类里的全局变量——类属性(见 十九 ,嵌套函数里声明外部变量 nonelocal)
- 调用类的属性、方法都要“self.” “self.” “self.” !!!
- 二叉搜索树 — 中序遍历可得由小到大的序列,其他题见 二十三、二叉搜索树中的众数
| |
十二、二叉树层序遍历II
107. 二叉树的层序遍历 II - 力扣(LeetCode)
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
| |
十三、合并二叉树
**递归:1. 假定往下传一些参数,他能返回理想值,2. 当前层,接收了上层参数,确保能返回理想值。**具体看代码注释,关键是 “处理好当前层内容,正确返回上层”
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
| |
十四、二叉树的层平均值
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。
| |
十五、找出克隆二叉树中的相同节点
1379. 找出克隆二叉树中的相同节点 - 力扣(LeetCode)
给你两棵二叉树,原始树 original 和克隆树 cloned,以及一个位于原始树 original 中的目标节点 target。
其中,克隆树 cloned 是原始树 original 的一个 副本 。
请找出在树 cloned 中,与 target 相同 的节点,并返回对该节点的引用(在 C/C++ 等有指针的语言中返回 节点指针,其他语言返回节点本身)。
What I have leaned:
- 声明全局变量 未必比 传形参要好,因为python都是传引用,除非对其修改 才可能 创建副本
- TreeNode可以直接进行比较,实际比较的是 物理内存地址(指针),target和original指到同一处时,original is target才为True,cloned is target永远为False
| |
十六、图像渲染
有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。你也被给予三个整数 sr , sc 和 color 。你应该从像素 image[sr][sc] 开始对图像进行上色 填充 。
为了完成 上色工作:
- 从初始像素开始,将其颜色改为
color。 - 对初始坐标的 上下左右四个方向上 相邻且与初始像素的原始颜色同色的像素点执行相同操作。
- 通过检查与初始像素的原始颜色相同的相邻像素并修改其颜色来继续 重复 此过程。
- 当 没有 其它原始颜色的相邻像素时 停止 操作。
最后返回经过上色渲染 修改 后的图像 。
what you learn:
- 初始像素与目标颜色相同。不会对图像进行任何更改。
- 找一种中序遍历的感觉
| |
十七、N 叉树的最大深度
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
What I learn:
- 不要掉入惯性思维的坑,仔细看它定义的类(结构体)是怎样的,此题没有空结点(对比 二叉树最大深度 的 定义,它不是子树结点为None而是存子树的list为None,因此,无法实现 “当前结点为None”这种做法。
- 这就引出在递归结尾处的两种处理,一种是“如果子树不为空,才进入递归函数”,另一种是“子树结点传进递归函数,函数开头判断是否为空”,我称前者为“预判处理”,后者为“当前层处理”。二者看情况用,通常用“当前层处理”,但这里不行,因为没有 空结点。
| |
十八、单值二叉树
如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。
只有给定的树是单值二叉树时,才返回 true;否则返回 false。
What I learn:
方法里定义的临时变量(代码里的 v ),对于方法里的函数(哪怕递归了)是全局的。
—— 当
dfs被定义为嵌套函数时,它可以访问外部函数isUnivalTree中的变量v(闭包机制)—— 如果将
dfs改为实例方法(如self.dfs),不能访问外部函数的变量v—— 还要看是访问的变量类型,不可变类型:可读,要写需nonlocal;
可变类型:可读可写
如果有多个、多重if else,看看能不能用 or and 代替
定义参数时不要和外边变量重名,如:def dfs(node): 而非 def dfs(root):
| |
十九、二叉树的堂兄弟节点
在二叉树中,两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。
我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。
只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。
What I learn:
当你想得清楚当前层的处理时,发现要上层数据,想想能不能传递上层数据下去
嵌套函数 对 外部作用域变量的访问 的关键区别:读取和修改。
只要函数内部存在对不可变类型变量
pre_dep的 修改(如pre_dep = dep),Python 默认将其视为 局部变量。仅读取,则会视为外部变量(见 十八 的 v)
| |
二十、寻找图中是否存在路径
1971. 寻找图中是否存在路径 - 力扣(LeetCode)
有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。
存在 有效路径 ,则返回 true,否则返回 false 。
| |
What I learn:
- defaultdict(list)将“边数据”转为“点数据”,复杂度由 n^2 变 n,边=n(n-1)/2=n^2
| |
二十一、另一棵树的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

What I learn:
- 探索必要条件:如果是子树,那就有相同树,则二者深度必然相等,也就是深度相等的结点才需判断是不是相同树。 此必要条件能大幅减小复杂度。
| |
二十二、二叉树中第二小的节点
671. 二叉树中第二小的节点 - 力扣(LeetCode)
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,即 root.val = min(root.left.val, root.right.val) 总成立。
给出这样的一个二叉树,你需要输出所有节点中的 第二小的值 。
如果第二小的值不存在的话,输出 -1 。

| |
What I learn
假设递归终点正确返回,处理中间过程:
我们知道root.val是最小值,那么
第二小的值为 较小的子节点那一边的子树的第二小的值 或 更大的子节点 之中
即min(get_sec_min(较小结点), 较大结点.val)
因为当前层是考虑了子节点的,所以是“预判式递归”
处理末尾情况,使“数学归纳法”成立:
“预判式递归”考虑倒数两层,1. 最底一层因为有空叶子而需要特殊处理。2. 倒数第二层(中间过程)要看看最底一层的返回是否兼容(即min(get_sec_min(较小结点), 较大结点.val)是否合理)
这里就是:
child有返回-1时要取max,或者
新写一个函数,叶子返回inf,外部再处理返回inf的情况。
“限制性递归”就不是每个子节点都进入,需要“预判处理”,见 [十七、N叉树的最大深度](#十七N 叉树的最大深度)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: # 会从沿着值为root.val的路线往下走,总共考虑 是/否为叶子、较大/较小子树方向 # 如果当前是值为root.val的叶子返回-1,如果当前是值为不为root.val的叶子的情况不存在 def findSecondMinimumValue(self, root: TreeNode) -> int: # 必然不存在第二小的值 if not root or not root.left: return -1 # 我们知道root.val是最小值,那么 # 第二小的值存在于 更小的子节点那一边的子树的第二小的值 或 更大的子节点 之中 left = root.left.val if root.left.val != root.val \ else self.findSecondMinimumValue(root.left) right = root.right.val if root.right.val != root.val \ else self.findSecondMinimumValue(root.right) return min(left, right) if left != -1 and right != -1 else max(left, right)
二十三、二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树

What the hell did I learn:
二叉搜索树 — 中序遍历可得由小到大的序列,相当于将树拍平在数轴上,
如 1, 2, 2, 4, 6, 6, 9,递归每层相当于指针指到某处
| |
二十四、表格里单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

| |
What I learn:
变量作用域和函数传参总结:
- 函数传参传 指针/别名,一定是。
- 对于外部不可修改变量:
- 若传参了
- 函数内若修改变量,复制生成局部变量,不影响外部变量,读取读局部变量。
- 函数内若只读取变量,不生成局部变量,指针方式读。
- 若没传参
- 函数内若修改变量,报错不存在该局部变量。
- 函数内若只读取变量,自动指针方式读外部变量。
- 若传参了
- 对于外部可修改变量:
- 无论传没传参,修不修改,一律别名方式读取或修改外部变量。
树型标表示
graph TD A[外部变量] --> B[不可修改变量] A --> C[可修改变量] B --> D[传参] B --> E[不传参] D --> F[(函数内)修改变量] D --> G[只读变量] F --> F1[复制生成局部变量] G --> G1[指针方式读外部变量] E --> H[函数内修改变量] E --> I[函数内只读变量] H --> H1[报错:不存在该局部变量] I --> I1[指针方式读外部变量] C --> J[无论传参与否] J --> K[无论修改与否] K --> L[别名方式读取或修改外部变量]- 不可改-传参-修改-复制生成局部变量
- 不可改-传参-只读-指针方式读
- 不可改-不传参-修改-报错
- 不可改-不传参-只读-指针方式读
- 可改 - 传参/不传参 - 修改/不修改 - 别名方式
遍历各方向 并 进入各方向的递归函数:
1 2 3 4# 相邻格子 for x, y in (i, j - 1), (i, j + 1), (i - 1, j), (i + 1, j): if 0 <= x < m and 0 <= y < n and dfs(x, y, k + 1): return True注意这题里 由于访问可修改变量,一定是别名访问,需要“恢复现场”,即回溯
any 是否存在True all 是否都为True
| |
二十五、二叉树化单链表
面试题 17.12. BiNode - 力扣(LeetCode)
二叉树数据结构TreeNode可用来表示单向链表(其中left置空,right为下一个链表节点)。实现一个方法,把二叉搜索树转换为单向链表,要求依然符合二叉搜索树的性质,转换操作应是原址的,也就是在原始的二叉搜索树上直接修改。
返回转换后的单向链表的头节点。
What I learn:
- 创建单链表思想:设置头结点,pre初始=头结点,创建当前结点,pre内指针 指向 当前结点,pre=当前结点
| |
二十六、找到所有的农场组
给你一个下标从 0 开始,大小为 m x n 的二进制矩阵 land ,其中 0 表示一单位的森林土地,1 表示一单位的农场土地。
为了让农场保持有序,农场土地之间以矩形的 农场组 的形式存在。每一个农场组都 仅 包含农场土地。且题目保证不会有两个农场组相邻,也就是说一个农场组中的任何一块土地都 不会 与另一个农场组的任何一块土地在四个方向上相邻。
land 可以用坐标系统表示,其中 land 左上角坐标为 (0, 0) ,右下角坐标为 (m-1, n-1) 。请你找到所有 农场组 最左上角和最右下角的坐标。一个左上角坐标为 (r1, c1) 且右下角坐标为 (r2, c2) 的 农场组 用长度为 4 的数组 [r1, c1, r2, c2] 表示。
请你返回一个二维数组,它包含若干个长度为 4 的子数组,每个子数组表示 land 中的一个 农场组 。如果没有任何农场组,请你返回一个空数组。可以以 任意顺序 返回所有农场组。

| |
what I learn:
- 试探性 行为"x1+1" “y1+1”,不满足则x1 y1抵达边界
- 多变量时,分清楚“域内变量”是什么?
| |
二十七、路径总和 II
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。

| |
what I learn:
- python里的 可修改变量 和 不可修改变量 的赋值,可修改-创建新变量,不可修改-别名
| |
- copy.deepcopy实现 不可修改变量的 “创建变量赋值”
| |
二十八、二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode,其中right子指针指向链表中下一个结点,而左子指针始终为null。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。

| |
what I learn:
二叉树先序、中序、后续遍历转二叉树总结:中序-尾插法-LCR处理,后序-尾插法-LRC处理,先序-头插法-RLC处理
原理:确保 pre的右子树的处理 不会影响后续结点的访问,确保 cur的左子树的处理 不会影响后续结点的访问。
L(进入left-child) C(处理current) R(进入right-child)
- 中序-尾插法-LCR,即搜索二叉树遍历。
1 2 3 4 5dfs(node.left) pre.right=node node.left=None pre=node dfs(node.right)- 后序-尾插法-LRC
1 2 3 4 5dfs(node.left) dfs(node.right) pre.right=node node.left=None pre=node- 先序-头插法-RLC 头插法实现将CLR转换为RLC,类后序,当前层处理不会影响后续 向父节点的 访问。
1 2 3 4 5dfs(node.right) dfs(node.left) node.right=head.right head.right=node node.left=None