请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
哦。。。又是一道没有做出来的题。。。直接跟着大神的思路写一遍吧
解题思路:
- 后序遍历定义:
[ 左子树 | 右子树 | 根节点 ]
,即遍历顺序为 “左、右、根” 。 - 二叉搜索树定义: 左子树中所有节点的值
<
<
< 根节点的值;右子树中所有节点的值
>
>
> 根节点的值;其左、右子树也分别为二叉搜索树。
方法一:递归分治 - 根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
递归解析:
- 终止条件: 当 i ≥ j i \geq j i≥j ,说明此子树节点数量 ≤ 1 \leq 1 ≤1 ,无需判别正确性,因此直接返回 t r u e true true ;
- 递推工作:
- 划分左右子树: 遍历后序遍历的 [ i , j ] [i, j] [i,j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 m m m 。此时,可划分出左子树区间 [ i , m − 1 ] [i,m-1] [i,m−1] 、右子树区间 [ m , j − 1 ] [m, j - 1] [m,j−1] 、根节点索引 j j j 。
- 判断是否为二叉搜索树:
- 左子树区间
[
i
,
m
−
1
]
[i, m - 1]
[i,m−1] 内的所有节点都应
<
p
o
s
t
o
r
d
e
r
[
j
]
< postorder[j]
<postorder[j]。而第
1.划分左右子树
步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。 - 右子树区间 [ m , j − 1 ] [m, j-1] [m,j−1] 内的所有节点都应 > p o s t o r d e r [ j ] > postorder[j] >postorder[j] 。实现方式为遍历,当遇到 ≤ p o s t o r d e r [ j ] \leq postorder[j] ≤postorder[j] 的节点则跳出;则可通过 p = j p = j p=j 判断是否为二叉搜索树。
- 左子树区间
[
i
,
m
−
1
]
[i, m - 1]
[i,m−1] 内的所有节点都应
<
p
o
s
t
o
r
d
e
r
[
j
]
< postorder[j]
<postorder[j]。而第
- 返回值: 所有子树都需正确才可判定正确,因此使用 与逻辑符
and
连接。- p = j p = j p=j : 判断 此树 是否正确。
- r e c u r ( i , m − 1 ) recur(i,m−1) recur(i,m−1) : 判断 此树的左子树 是否正确。
- r e c u r ( m , j − 1 ) recur(m,j−1) recur(m,j−1) : 判断 此树的右子树 是否正确。
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def recur(left, root):
result = True
if left >= root:
return True
right_root = root
for i in range(left, root):
if postorder[i] > postorder[root]:
right_root = i
break
for i in range(right_root, root):
if postorder[i] < postorder[root]:
result = False
break
return result and recur(left, right_root - 1) and recur(right_root,root - 1 )
return recur(0, len(postorder) - 1)
方法二:辅助单调栈
- 后序遍历倒序:
[ 根节点 | 右子树 | 左子树 ]
。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。
- 设后序遍历倒序列表为
[
r
n
,
r
n
−
1
,
.
.
.
,
r
1
]
[r_{n}, r_{n-1},...,r_1]
[rn,rn−1,...,r1] ,遍历此列表,设索引为
i
i
i ,若为 二叉搜索树 ,则有:
- 当节点值 r i > r i + 1 r_i > r_{i+1} ri>ri+1 时: 节点 r i r_i ri 一定是节点 r i + 1 r_{i+1} ri+1 的右子节点。
- 当节点值 r i < r i + 1 r_i < r_{i+1} ri<ri+1 时: 节点 r i r_i ri 一定是某节点 r o o t root root 的左子节点,且 r o o t root root 为节点 r i + 1 , r i + 2 , . . . , r n r_{i+1}, r_{i+2},..., r_{n} ri+1,ri+2,...,rn 中值大于且最接近 r i r_i ri 的节点(∵ r o o t root root 直接连接 左子节点 r i r_i ri)。
- 当遍历时遇到递减节点 r i < r i + 1 r_i < r_{i+1} ri<ri+1 ,若为二叉搜索树,则对于后序遍历中节点 r i r_i ri 右边的任意节点 r x ∈ [ r i − 1 , r i − 2 , . . . , r 1 ] r_x \in [r_{i-1}, r_{i-2}, ..., r_1] rx∈[ri−1,ri−2,...,r1] ,必有节点值 r x < r o o t r_x < root rx<root 。
节点 r x r_x rx 只可能为以下两种情况:① r x r_x rx 为 r i r_i ri 的左、右子树的各节点;② r x r_x rx 为 r o o t root root 的父节点或更高层父节点的左子树的各节点。在二叉搜索树中,以上节点都应小于 r o o t root root。
- 遍历 “后序遍历的倒序” 会多次遇到递减节点 r i r_i ri ,若所有的递减节点 r i r_i ri 对应的父节点 r o o t root root 都满足以上条件,则可判定为二叉搜索树。
- 根据以上特点,考虑借助 单调栈 实现:
- 借助一个单调栈 s t a c k stack stack 存储值递增的节点;
- 每当遇到值递减的节点 r i r_i ri ,则通过出栈来更新节点 r i r_i ri 的父节点 r o o t root root;
- 每轮判断
r
i
r_i
ri 和
r
o
o
t
root
root 的值关系:
- 若 r i > r o o t r_i > root ri>root 则说明不满足二叉搜索树定义,直接返回 f a l s e false false。
- 若 r i < r o o t r_i < root ri<root 则说明满足二叉搜索树定义,则继续遍历。
算法流程:
- 初始化: 单调栈 s t a c k stack stack ,父节点值 r o o t = + ∞ root = +\infin root=+∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
- 倒序遍历
p
o
s
t
o
r
d
e
r
postorder
postorder:记每个节点为
r
i
r_i
ri;
- 判断: 若 r i > r o o t r_i>root ri>root ,说明此后序遍历序列不满足二叉搜索树定义,直接返回 f a l s e false false;
- 更新父节点 r o o t root root : 当栈不为空 且 r i < s t a c k . p e e k ( ) r_i<stack.peek() ri<stack.peek() 时,循环执行出栈,并将出栈节点赋给 r o o t root root 。
- 入栈: 将当前节点 r i r_i ri入栈;
- 若遍历完成,则说明后序遍历满足二叉搜索树定义,返回 t r u e true true 。
class Solution:
def verifyPostorder(self, postorder: [int]) -> bool:
stack, root = [], float("+inf")
for i in range(len(postorder) - 1, -1, -1):
if postorder[i] > root: return False
while(stack and postorder[i] < stack[-1]):
root = stack.pop()
stack.append(postorder[i])
return True
第二种解法目前没有看懂,仅作记录