二叉树的问题主要涉及到两大类思路,遍历和递归。
二叉树的前提下,“递归”不再是以
for循环的情况出现1,而是回归到其本质:每个节点都看一眼,并且不断维护和更新外部变量。遍历有点类似一个指针在树上游走
Note
二叉树的递归和遍历,遍历和递归都是使用的同一套代码模板,也都是不断调用同一个函数来实现,但是思想上是不一样。 目前我的感觉是这样:遍历没有返回值,需要维护外部变量,比如定义一个全局的结果变量,遍历的过程中直接操作这个全局的结果变量;而递归是有返回值的,我们需要做的是分解问题,重复利用起每个子问题的返回值。
应用递归和遍历,对应两种不同的思路。
- 能不能我把每个节点看一遍,就能得到答案。 → 遍历
- 问题能不能分解成左右子树的子问题? → 递归
在每个思路下,我们需要想的是下面的问题:
- 每个节点上,我应该干什么?添加怎样的代码?
- 我应该在前序位置操作,还是应该在后序位置操作2 ?
Note
在前序位置操作的,一般对于操作位置不敏感,即放在哪儿都行。 在后序位置操作的,是需要得知左右子树的信息之后才能进行的操作。
递归解决二叉树问题的步骤
- 并不是所有的二叉树的问题都可以递归解决
- 递归解决的二叉树问题一定可以分解成最优子结构的子问题
按照如下步骤考虑:
- 分解成子问题,每个树,我需要知道子树的什么信息就能解决当前的子问题了?
注意递归返回的信息不管左右树都应该是一样,如果需要的信息左右树的不一样,就全都返回来(先当成黑盒),在取所需
- 根据拿到的信息,在后序的位置解决这个子问题
可能需要很多判断语句
- 想一想 base case 的返回值应该是什么。如果不知道base case返回什么,就可以试试返回空,然后在后序的位置拿到信息后判断一下。
- 制作当前节点的返回值,需要和1中是一样的
- 递归调用就行了
例子 1
思考流程
- 分解子问题:一个树是二叉搜索树,那么其每个节点一定满足:①左子树是二叉搜索树 ②右子树是二叉搜索树 ③左子树的最大值小于当前节点值 ④右子树的最小值大于当前节点值。
可以看到,对于左右子树的信息,我们需要的是不一样的(左子树要最大值,右子树要最小值),那么干脆最大最小值都返回来。 综上,我们需要左右子树的信息是:是不是二叉搜索树、最大值、最小值
- 根据拿到的信息判断当前节点为根节点的树是不是二叉搜索树
- 思考base case
- 制作当前节点的返回值
- 递归开始
代码
# 1. 返回的结构体,返回三个信息
class Info():
def __init__(self, isBST, mi, ma):
self.isBST = isBST
self.min = mi
self.max = ma
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def recur(node):
# 3. base case
if node == None:
# 这里不能返回Info(True, -float('inf'), float('inf')),不然出错
return Info(True, float('inf'), -float('inf'))
left = recur(node.left)
right = recur(node.right)
#2. 解决当前子问题
isBST = False
if (node.val > left.max) and (node.val < right.min) and (left.isBST) and (right.isBST):
isBST = True
#4. 获取当前节点返回值
mi = min(node.val, left.min, right.min)
ma = max(node.val, left.max, right.max)
return Info(isBST, mi, ma)
# 5. 递归开始
out = recur(root)
return out.isBSTTips
在应用递归的时候,如果需要结合回溯思想,在外部维护一个辅助变量,要记得在进入和离开一个节点的时候,及时的添加和弹出相关的内容,相关问题可以看看路径总和II中对于path的维护