摘自labuladong的算法小抄:https://labuladong.online/algo/essential-technique/binary-tree-summary-2/
void traverse(TreeNode* root){
if (!root) { return; }
// 前序位置
traverse(root->left);
// 中序位置
traverse(root->right);
// 后序位置
}
traverse
是一个遍历函数,作用就是遍历二叉树的所有节点,前中后序决定了遍历的顺序。数组和链表也可以用类似的方法来遍历。
因为二叉树不容易写成迭代的形式,所以一般我们说二叉树的遍历,就是指递归遍历。
前序位置:刚进入一个结点的时候。(类比三国杀,回合开始的时候)
中序位置:遍历完左子树,刚要开始遍历右子树的时候。所以多叉树没有中序遍历
后序位置:要离开一个结点的时候。(类比三国杀,回合结束的时候)
后序遍历的应用:倒序打印链表
void print_list_node(ListNode* head) {
if(head == NULL) {
return;
}
print_list_node(head->next);
print(head->val);
}
每个节点都有自己的前中后序遍历的位置
这里的前序不仅仅是教科书上说的前序遍历(中序、后序同理)。你可以在遍历二叉树时在前序位置往列表里插入元素,这就是教科书上的前序遍历。但不代表你不能在前序位置做更复杂的事情,实现更复杂的功能。
遍历思路解法:用一个外部变量记录res遍历结果,新建一个辅助函数traverse,返回值为空
def preOrder(root):
traverse(root)
return res
def traverse(root):
if (root == None):
return []
res.append(root.val)
traverse(root.left)
traverse(root.right)
分解思路:不用辅助函数,不用外部变量,preOrder函数返回的是以root为根节点子树的前序遍历结果。这种方法一般不常用,因为各个语言list add的操作的复杂度不可控。
def preOrder2(root):
if root == None:
return []
res = []
res.append(root.val)
res.extend(preOrder2(root.left))
res.extend(preOrder2(root.right))
return res
这里引出一个重点,什么时候要用辅助函数traverse,什么时候直接用原函数递归?
结论:如果你的问题需要用一个外部函数记录,一般都需要用辅助函数traverse;如果题目要求的答案恰好就是函数本身的返回值,就不用外部函数traverse.
leetcode 204
分解的思路,先算出左右子树的最大深度,再+1,因此逻辑要放在后序位置。
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL){
return 0;
}
int depth = max(maxDepth(root->left), maxDepth(root->right));
depth ++;
return depth;
}
};
其实这道题也可以用遍历的思路来解,代码写起来比较麻烦. 核心思路就是遍历整个二叉树,用一个外部变量depth记录当前结点的深度。如果到达叶子结点则更新最终的结果res.
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def __init__(self, res=0, depth=0):
self.res = res
self.depth = depth
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.traverse(root)
return self.res
def traverse(self, root):
if root == None:
self.res = max(self.res, self.depth)
return self.res
self.depth += 1
self.traverse(root.left)
self.traverse(root.right)
self.depth -= 1
提炼一个通用的解法
void solution(TreeNode* root){
print_node(root, 1);
}
void print_node(TreeNode* root, int pos){
if(root == NULL) {
return;
}
cout << pos << endl;
print_node(root->left, pos+1);
print_node(root->right, pos+1);
}
int solution(TreeNode* root) {
if(root == NULL){
return 0;
}
int left = solution(root->left);
int right = solution(root->right);
return left + right + 1;
}
这两个问题的根本区别就是前序和后序。
换句话说只要问题和子树有关,大概率是要给函数设置合理的定义和返回值,在后序的位置写代码。
重要:每一条二叉树的直径,就是某个节点的左右子树深度之和。
需要一个全局变量来记录。
class Solution(object):
def diameterOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.res = 0
self.traverse(root)
return self.res
def traverse(self, root):
if root == None:
return 0
leftMax = self.traverse(root.left)
rightMax = self.traverse(root.right)
self.res = max(self.res, leftMax + rightMax)
return max(leftMax, rightMax) + 1
这里traverse函数的作用,是返回root的最大深度。时间复杂度O(n), n是二叉树的所有节点数。
这道题必须增加辅助函数,无法用分解的方式来做。因为直径无法分解为子树的问题。就算求出了左右子树的直径,也无法求出当前数的直径。需要用外部变量来记录res,用遍历的方式来做。
一些有趣的:https://leetcode.com/problems/diameter-of-binary-tree/solutions/575172/worst-solution-ever-worse-than-100-of-submissions-in-both-time-and-memory/
leetcode上这个老哥写了个巨复杂的解法,居然是点赞最高的答案。评论区:Once you start writing this in an interview you are no longer in danger, you are the danger.
Thanos had to snap twice to wipe out this code
https://leetcode.cn/problems/diameter-of-binary-tree/
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if not root:
return
res = []
q = deque()
q.append(root)
while q:
sz = len(q)
level = []
for i in range(sz):
cur = q.popleft()
level.append(cur.val)
if cur.left:
q.append(cur.left)
if cur.right:
q.append(cur.right)
res.append(level)
return res