1. 平衡二叉树(leetcode 110)
递归:
大家也明白了,比较高度,我们要使用后序遍历。
递归三部曲开始分析:
1. 函数返回值和参数:传入当前节点,返回当前节点高度。如果当前节点左右高度差大于一,我们直接返回-1终止函数。
2. 终止条件,若空节点,高度为0
3. 单层递归逻辑:分别求出左右子树高度,若差值大于一,则return -1。若左右子树有-1的高度的话,也直接return -1。这一步也是要记得处理的,不然之后可能会出错
class Solution:
def getheight(self, root):
if not root:
return 0
left = self.getheight(root.left)
if left == -1: return -1
#这边不设置的话,之后会可能会继续判断两树高度。要先给他处理了。
right = self.getheight(root.right)
if right == -1: return -1
if abs(left-right) > 1:
return -1
return max(left, right) + 1
def isBalanced(self, root: Optional[TreeNode]) -> bool:
if not root:
return 1
if self.getheight(root) == -1:
return 0
else:
return 1
迭代:
这题不同于上一题的用层序遍历求深度,我们可以从上往下层序遍历。我们这次要求的是每个节点的左右节点高度,我们只能从下往上。因此我们无法用层序遍历来解决这道题。所以我们只能用栈来模拟后序遍历,设计一个迭代的方程来算出每个节点的高度。然后验证左右的高度差是否为一。
大方向上还是和递归的思路差不多的。但这题使用栈的话我们会进行很多的重复运算,时间效率比较低,所以不做推荐。
2. 二叉树的所有路径(leetcode 257)
这道题我们要找的是根节点到叶子节点的路径,需要使用前序遍历,从上往下走。
在此题中我们将第一次涉及到回溯,因为我们要把每个路径都记录下来,需要通过回溯来退回原来的路径,并进入新的路径。
我们先使用递归做前序遍历。递归回溯是一家的,本题也需要用到回溯。
递归:
三部曲
1. 确认参数以及返回值。我们想要传入根节点,记录每条路径的path,和存放结果集的result。递归不需要返回值
2. 确认递归终止条件。本题的终止条件为找到叶节点,也就是左右孩子为空的时候。
if cur.left == None and cur.right == None:
终止逻辑
为什么我们不判断cur是否为空呢?因为下面的逻辑可以控制空节点不进入循环。
再来看一下终止处理的逻辑。
我们这里使用list结构来记录每条path,并最终放进我们的result list里面。
我们为什么使用list来记录路径是因为接下来处理单层递归逻辑的时候要做回溯,list方便。
if not cur.left and not cur.right:
sPath = '->'.join(map(str, path))
result.append(sPath)
return
3. 确定单层递归逻辑:因为哦我们使用的是前序遍历,要优先处理中间节点。所以我们要先将我们的cur节点放入path中。
然后是递归和回溯。我们在中止条件那边没有判断cur是否为空,因此在递归的时候如果为空就不进行下一层递归了。因此递归前要加上判断语句,判断我们的下一层cur是否为空。
递归完我们就要进行回溯。我们要知道回溯和递归是一一对应的,有一个递归就有一个回溯。因此我们也应该将回溯写进我们的递归逻辑中,不能拆开。
因此整体的完整版代码如下:
class Solution:
def traversal(self, cur, path, result):
path.append(cur.val) #访问并处理中节点
if not cur.left and not cur.right: #到达叶子
sPath = '->'.join(map(str, path))
result.append(sPath)
return #完成路径,加入结果集
if cur.left: #左节点
self.traversal(cur.left, path, result) #左递归
path.pop() #回溯
if cur.right:
self.traversal(cur.right, path, result) #右递归
path.pop() #回溯
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
result = []
path = []
if not root:
return result
self.traversal(root, path, result)
return result
迭代法:
我们也可以用前序遍历的迭代方式来模拟遍历路径。我们用栈来进行模拟递归,和回溯,存放遍历路径。我们创建一个stack来遍历且处理我们每次访问到的节点,同时我们用一个path stack来记录我们到达每个节点后的当前路径。因此当我们在到达叶节点时,我们就可以把当前路径加入我们的result中,同时删除这个记录,来达到我们回溯的目的,这样我们就能以前序的顺序获得所有路径。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root:
return []
stack = [root]
path_st = [str(root.val)]
result = []
while stack:
cur = stack.pop()
path = path_st.pop()
#如果当前为叶节点,添加到result
if not cur.left and not cur.right:
result.append(path)
if cur.right:
stack.append(cur.right)
path_st.append(path + '->' + str(cur.right.val))
if cur.left:
stack.append(cur.left)
path_st.append(path + '->' + str(cur.left.val))
return result
3. 左叶子之和(leetcode 404)
这题我们只需要左叶子,所以判定有两个条件,首先是该节点为左节点,且该节点没有孩子。
递归法:
1. 参数就是我们的根节点,返回值就是左叶子之和
2. 终止条件为空节点。
3. 单层递归逻辑:如果遇到左叶子,记录数值,然后通过递归取左子树的左叶子和,以及右子树的左叶子之和。
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
leftval = 0 #初始化左叶子值,有的root没有左叶子
if root.left and (not root.left.left and not root.left.right):
leftval = root.left.val
#中左右结构递归。
return leftval + self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
这边也补充一下迭代法的写法,同样是前序遍历,处理好判断条件就行
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
stack = [root]
leftsum = 0
while stack:
cur = stack.pop()
if cur.left and (not cur.left.left and not cur.left.right):
leftsum += cur.left.val
if cur.left and (cur.left or cur.right):
stack.append(cur.left)
if cur.right and (cur.left or cur.right):
stack.append(cur.right)
return leftsum
4. 完全二叉树的节点个数(leetcode 222)
这题也是比较简单,我们右递归按前序的方式向下,沿途访问一个节点,count+1就行。
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
return self.countNodes(root.left) + self.countNodes(root.right) +1
迭代:
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
count = 0
stack = [root]
while stack:
cur = stack.pop()
count += 1
if cur.left: stack.append(cur.left)
if cur.right: stack.append(cur.right)
return count
bfs:
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
count = 0
queue = collections.deque([root])
while queue:
for _ in range(len(queue)):
cur = queue.popleft()
count += 1
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
return count
完全二叉树写法:
以上三种写法是我们遍历普通二叉树的方式,时间复杂度为O(n)但我们要注意到这题的数是完全二叉树。完全二叉树只有最底层会没满,前面的第n层每层包含了2*n个节点。若全满为满二叉树。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。
class Solution:
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
count = 1
left = root.left
right = root.right
while left and right:
count += 1
left = left.left
right = right.right
if not left and not right:
return 2 ** count -1
return self.countNodes(root.left) + self.countNodes(root.right) + 1