以下都是递归方法进行实现。
递归三部曲:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
LeetCode 226.翻转二叉树
思路:
1. 到达处理节点时,翻转处理节点的子节点
2. 如何去遍历处理节点 —— 可以采用前中后序遍历。但中序遍历存在坑,因此推荐前后序遍历。
分析:
前序遍历:
先处理根节点,再去分别处理左右两边。到达左右两边后,也按照相同的顺序进行递归操作。直到发现处理节点无左节点和右节点。——— 从上到下
print("root", root)
root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
self.invertTree(root.left) ### 遍历左节点
self.invertTree(root.right) ### 遍历右节点
('root', TreeNode{val: 4, left: TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}, right: TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}})
('root', TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}})
('root', TreeNode{val: 9, left: None, right: None})
('root', TreeNode{val: 6, left: None, right: None})
('root', TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}})
('root', TreeNode{val: 3, left: None, right: None})
('root', TreeNode{val: 1, left: None, right: None})
后序遍历:

先分别处理左右两边,再往上递归去处理上面。—— 从下到上,如1和3无子节点,因此弹出到节点2去进行处理...
self.invertTree(root.left) ### 遍历左节点
self.invertTree(root.right) ### 遍历右节点
print("root", root)
root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
('root', TreeNode{val: 1, left: None, right: None})
('root', TreeNode{val: 3, left: None, right: None})
('root', TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}})
('root', TreeNode{val: 6, left: None, right: None})
('root', TreeNode{val: 9, left: None, right: None})
('root', TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}})
('root', TreeNode{val: 4, left: TreeNode{val: 2, left: TreeNode{val: 3, left: None, right: None}, right: TreeNode{val: 1, left: None, right: None}}, right: TreeNode{val: 7, left: TreeNode{val: 9, left: None, right: None}, right: TreeNode{val: 6, left: None, right: None}}})
中序遍历:
先处理左边,从下到上去处理中间节点,再去处理右边。但到中间处理节点时,此时左右子树已经发生变化,如果你此时再去遍历右子树。则漏了另一半的树进行翻转操作,这就是中序遍历的坑所在。
self.invertTree(root.left) ### 遍历左节点
print("root", root)
root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
self.invertTree(root.left)
### 遍历右节点, 但这里存在坑,因为你上述对left进行了更换,你此时再去遍历right的话,相当于是去翻转已经被翻转过的节点了
('root', TreeNode{val: 1, left: None, right: None})
('root', TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}})
('root', TreeNode{val: 3, left: None, right: None})
('root', TreeNode{val: 4, left: TreeNode{val: 2, left: TreeNode{val: 3, left: None, right: None}, right: TreeNode{val: 1, left: None, right: None}}, right: TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}})
('root', TreeNode{val: 6, left: None, right: None})
('root', TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}})
('root', TreeNode{val: 9, left: None, right: None})
# 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 invertTree(self, root):
"""
:type root: Optional[TreeNode]
:rtype: Optional[TreeNode]
"""
### 思路:到达处理节点时,交换其子节点的左右顺序。
### 遍历处理节点的方式可以采用前中序进行遍历,但是中序遍历有坑,需要注意,不推荐使用。
if not root: ### 空节点。终止条件 + 递归函数输入参数
return root #递归函数返回值
## 前序遍历实现方式 ———— 单次递归逻辑
print("root", root)
root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
self.invertTree(root.left) ### 遍历左节点
self.invertTree(root.right) ### 遍历右节点
## 后序遍历实现方式
# self.invertTree(root.left) ### 遍历左节点
# self.invertTree(root.right) ### 遍历右节点
# print("root", root)
# root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
## 中序遍历实现方式
# self.invertTree(root.left) ### 遍历左节点
# print("root", root)
# root.left, root.right = root.right, root.left ### 到达处理节点时,对子节点的操作
# self.invertTree(root.left) ### 遍历右节点, 但这里存在坑,已经你上述对left进行了更换,你此时再去遍历right的话,相当于是去翻转已经被翻转过的节点了
return root
LeetCode 101. 对称二叉树
思路:
- 先分别比较左右,从将左右的比较信息传递给中间节点 —— 后序遍历。只有后序遍历可以解决这道题;前序遍历中左右,无法在根节点的时候就直接判断左子树和右子树是否对称;中序遍历左中右,左子树的信息传递给根节点,但此时还没比较右子树,因此也不能判断是否二叉树是否对称。
- 由于对称二叉树是对称翻转的,因此是左子树外侧与右子树外侧进行对比,左子树内侧和右子树内侧进行对比,如果这两个对比结果是True,则证明这颗树是对称二叉树
算法:
- 左子树外侧与右子树外侧相比,即left的left指针与right的right指针相比。左子树内测与右子树内测相比,即left的right指针与right的left指针相比。
- 什么时候可以认为对称?不对称的情况有:① 外侧左节点空/非空、外侧右节点非空/空,因此不对称。②内测右节点空/非空、内测左节点非空/空,导致不对称。③外/内侧左右节点都非空,但值不同。对称的情况有:①外/内侧左右节点都为空。②外/内侧左右节点都非空,且值相等。
- 通过分别同步对左右子树进行递归操作(从上到下)来实现左右两边的判断。 并将最后的判断结果传递给中间节点,来判断这颗二叉树是否对称。
递归操作可视化:
先判断左右子树的最上层节点是否一样,如果不一样,直接return false。后续将分别需要去判断外侧和内测是否相同了,通过outside和inside实现。可以看到这个操作是从上到下的,分别将左右的判断结果赋予中间,对于外侧是左右中,对于内测是右左中,可以看到始终中是最后,因此对于对称的判断适合用后序遍历来进行实现。通过从上到下,从两边到中间的形式不断将比较结果反馈给中间。
print("left, right", left, right)
outside = self.compare(left.left, right.right) ### 让compare函数本身进行递归操作
inside = self.compare(left.right, right.left)
('left, right', TreeNode{val: 2, left: TreeNode{val: 3, left: None, right: None}, right: TreeNode{val: 4, left: None, right: None}}, TreeNode{val: 2, left: TreeNode{val: 4, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}})
('left, right', TreeNode{val: 3, left: None, right: None}, TreeNode{val: 3, left: None, right: None})
('left, right', TreeNode{val: 4, left: None, right: None}, TreeNode{val: 4, left: None, right: None})
Code:
# 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 isSymmetric(self, root):
"""
:type root: Optional[TreeNode]
:rtype: bool
"""
if not root: ### 判断是否空树
return root
result = self.compare(root.left, root.right)
return result
def compare(self, left, right): ## 递归函数输入参数
## 递归函数终止条件
if left == None and right != None: ### 单次判断一个处理节点是否对称的逻辑
return False
elif left != None and right == None:
return False
elif left == None and right == None:
return True
elif left and right:
if left.val != right.val: ### 后续需要递归判断外侧和内测是否相等,因此这里不能说直接判断如果相等,则return True。这样的话起不到递归效果,是直接通过判断根节点的子节点是否对称得来的。你后续outside和inside的递归操作没有得到执行。
return False
print("left, right", left, right)
outside = self.compare(left.left, right.right) ### 让compare函数本身进行递归操作
inside = self.compare(left.right, right.left)
result = outside and inside
return result ## 递归函数返回值
LeetCode 104.二叉树的最大深度
- 求树的高度:从下到上。 采用后序遍历实现,左右中。
- 求树的深度:从上到下。 采用前序遍历实现,中左右。
- 求树的最大深度 == 求树的高度
通过求树的高度来间接求树的最大深度方法 —— 后序遍历,递归法。
# 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 maxDepth(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
### 求树的高度:从下到上。 采用后序遍历实现,左右中。
### 求树的深度:从上到下。 采用前序遍历实现,中左右。
### 求树的最大深度 == 求树的高度
### 本题我们通过递归法,通过求树的高度来间接求得树的最大深度,采用后序遍历。
max_depth = self.get_Height(root)
return max_depth
def get_Height(self, root): ### 求树的高度
if root == None: ### 1.确定终止条件。为None时,此时高度为0,在上一层高度就为1了
return 0
left_height = self.get_Height(root.left) ### 后序遍历逻辑
right_height = self.get_Height(root.right)
height = 1 + max(left_height, right_height) ### 2.每次递归逻辑。每次后序遍历完后,传递到中间节点,高度+1
return height ### 确定返回参数是int 高度 和 输入参数是node 节点
这道题也可以通过层序遍历,通过迭代法实现。前序遍历也可以,但涉及到回溯,相对来说后序遍历在求树的最大深度上代码更简洁。
LeetCode 111.二叉树的最小深度
错误做法:忽略了最小深度的定义,根节点到叶子节点的最小路径。
如:,按错误做法的话,则会return的是1
# 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 minDepth(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
max_depth = self.get_Height(root)
return max_depth
def get_Height(self, root): ### 求树的高度
if root == None: ### 1.确定终止条件。为None时,此时高度为0,在上一层高度就为1了
return 0
left_height = self.get_Height(root.left) ### 后序遍历逻辑
right_height = self.get_Height(root.right)
# height = 1 + max(left_height, right_height) ### 2.每次递归逻辑。
height = 1 + min(left_height, right_height) ### 不能说直接改用求最大深度的方法来求最大,求最小深度存在坑,即是从叶子节点开始的。
return height ### 确定返回参数是int 高度 和 输入参数是node 节点
递归法实现,采用后序遍历,不同于求最大深度。在求最小深度时,我们要判断处理节点左子节点/右子节点为空的情况。就是我们不用同一的标尺,从最底叶子节点开始计算高度,而是让所有叶子节点根据自己本身位置开始计算当前位置到根节点的高度。
如上图中同一标尺,也就是求最大深度的方法中,节点3的高度为2。而采用自适应标尺的话节点3的高度为1。如果我们在最大深度的基础上将max(left_height, right_height)直接改为min(left_height, right_height)来求最小深度的话,就会陷入一个坑,忽略了是从叶子节点开始到根节点的距离。用min的话,你从左边NULL到根节点距离就为1,但最小深度定义是根节点到叶子节点的最小路径。
Code:
# 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 minDepth(self, root):
"""
:type root: Optional[TreeNode]
:rtype: int
"""
result = self.get_minHeight(root)
return result
def get_minHeight(self, root): ### 确定递归输入参数
if not root: ### 确定终止条件
return 0
left_height = self.get_minHeight(root.left)
right_height = self.get_minHeight(root.right)
### 确定单词递归逻辑
if root.left and not root.right: ### 左节点存在,右节点不存在
return 1 + left_height ### 左节点存在的话,那么如果左节点下有叶子节点的话,当前节点的高度就应该是这个值。
elif root.right and not root.left: ### 右节点存在,左节点不存在
return 1 + right_height ### 右节点存在的话,那么如果右节点下有叶子节点的话,当前节点的高度就应该是这个值。
return 1 + min(left_height, right_height) ### 确定返回参数,如果左右节点有叶子节点,那当前节点到根节点的最短路径就应该是这个值。
通过层序遍历,采用迭代法也可以解决这道问题。
在求深度问题上,层序遍历的实现思路更为统一。