代码随想录算法训练营Day13 | Leetcode 110平衡二叉树、257二叉树的所有路径、404左叶子之和、222完全二叉树的节点个数(递归解法)
一、翻转二叉树
相关题目:Leetcode110
文档讲解:Leetcode110
视频讲解:Leetcode110
1. Leetcode110.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
-
思路:
- 递归法:
- 明确递归函数的参数和返回值:
- 参数:当前传入节点。
- 返回值:以当前传入节点为根节点的树的高度。
- 标记左右子树是否差值大于 1:如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。所以如果已经不是二叉平衡树了,可以返回 -1 来标记已经不符合平衡树的规则了。
- 明确终止条件:
递归的过程中依然是遇到空节点了为终止,返回 0,表示当前节点为根节点的树高度为 0。 - 明确单层递归的逻辑:
分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回 -1,表示已经不是二叉平衡树了。
- 明确递归函数的参数和返回值:
- 递归法:
-
递归法
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if self.get_height(root) != -1:
return True
else:
return False
def get_height(self, root: TreeNode) -> int:
# Base Case
if not root:
return 0
# 左
if (left_height := self.get_height(root.left)) == -1:
return -1
# 右
if (right_height := self.get_height(root.right)) == -1:
return -1
# 中
if abs(left_height - right_height) > 1:
return -1
else:
return 1 + max(left_height, right_height)
#精简版:
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
return self.get_hight(root) != -1
def get_hight(self, node):
if not node:
return 0
left = self.get_hight(node.left)
right = self.get_hight(node.right)
if left == -1 or right == -1 or abs(left - right) > 1:
return -1
return max(left, right) + 1
二、二叉树的所有路径
相关题目:Leetcode257
文档讲解:Leetcode257
视频讲解:Leetcode257
1. Leetcode257.二叉树的所有路径
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
说明:叶子节点 是指没有子节点的节点。
-
思路:
- 题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。题目涉及到回溯,因为要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。前序遍历以及回溯的过程如图:
- 递归法:
- 递归函数参数以及返回值:要传入根节点,记录每一条路径的 path,和存放结果集的 result,这里递归不需要返回值。
- 确定递归终止条件:
- 当找到叶子节点,就开始结束的处理逻辑了(把路径放进 result 里),即当 cur 不为空,其左右孩子都为空的时候,就找到叶子节点。(不判断 cur 是否为空)
- 终止处理:使用 list 结构 path 来记录路径,所以要把 list 结构的 path 转为 string 格式,再把这个 string 放进 result 里。
- 确定单层递归逻辑:
- 因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进 path 中。
- 第一步没有判断 cur 是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。所以递归前要加上判断语句,保证递归节点非空。
- 回溯和递归是一一对应的,有一个递归,就要有一个回溯。
- 题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。题目涉及到回溯,因为要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。前序遍历以及回溯的过程如图:
-
注意:
- 递归法+隐形回溯(版本一):
每次递归调用时传入 path[:] 的拷贝,避免因为回溯或修改同一个列表而导致路径数据混乱。这种方式虽然避免了手动进行回溯(即不需要调用 path.pop()),但可能会产生额外的内存开销。 - 递归法+隐形回溯(版本二):
使用 path + ‘->’ 来拼接路径,这样确保了每条路径格式正确,如 “1->2->5”。这种方法相比于使用列表存储路径再转换为字符串的方式,直接利用字符串拼接,使代码更为简洁,但可能在字符串拼接较多时效率略低。
- 递归法+隐形回溯(版本一):
-
递归法
##递归法+回溯
# Definition for a binary tree node.
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):
result = []
path = []
if not root:
return result
self.traversal(root, path, result)
return result
##递归法+隐形回溯(版本一)
from typing import List, Optional
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root:
return []
result = []
self.traversal(root, [], result)
return result
def traversal(self, cur: TreeNode, path: List[int], result: List[str]) -> None:
if not cur:
return
path.append(cur.val)
if not cur.left and not cur.right:
result.append('->'.join(map(str, path)))
if cur.left:
self.traversal(cur.left, path[:], result)
if cur.right:
self.traversal(cur.right, path[:], result)
##递归法+隐形回溯(版本二)
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
path = ''
result = []
if not root: return result
self.traversal(root, path, result)
return result
def traversal(self, cur: TreeNode, path: str, result: List[str]) -> None:
path += str(cur.val)
# 若当前节点为leave,直接输出
if not cur.left and not cur.right:
result.append(path)
if cur.left:
# + '->' 是隐藏回溯
self.traversal(cur.left, path + '->', result)
if cur.right:
self.traversal(cur.right, path + '->', result)
三、左叶子之和
相关题目:Leetcode404
文档讲解:Leetcode404
视频讲解:Leetcode404
1. Leetcode404.左叶子之和
计算给定二叉树的所有左叶子之和。
-
思路:
- 注意是判断左叶子,不是二叉树左侧节点。
- 左叶子:节点 A 的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么 A 节点的左孩子为左叶子节点。
- 判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
- 递归法:
- 确定递归函数的参数和返回值:判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为 int,使用题目中给出的函数就可以了。
- 确定终止条件:如果遍历到空节点,那么左叶子值一定是 0,注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是 0。
- 确定单层递归的逻辑:当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和右子树左叶子之和,相加便是整个树的左叶子之和。
-
注意:
平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。 -
递归法
##递归
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
if root.left is None and root.right is None:
return 0
leftValue = self.sumOfLeftLeaves(root.left) # 左
if root.left and not root.left.left and not root.left.right: # 左子树是左叶子的情况
leftValue = root.left.val
rightValue = self.sumOfLeftLeaves(root.right) # 右
sum_val = leftValue + rightValue # 中
return sum_val
##递归精简版
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
leftValue = 0
if root.left is not None and root.left.left is None and root.left.right is None:
leftValue = root.left.val
return leftValue + self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
四、完全二叉树的节点个数
相关题目:Leetcode222
文档讲解:Leetcode222
视频讲解:Leetcode222
1. Leetcode222.完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
完全二叉树 的定义如下:
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。
-
思路:
- 普通二叉树(递归):
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为 int 类型。
- 确定终止条件:如果为空节点的话,就返回 0,表示节点数为 0。
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加 1 是因为算上当前中间节点)就是目前节点为根节点的节点数量。
- 普通二叉树(迭代):利用层序遍历进行迭代计数。
- 完全二叉树(递归):
- 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
- 情况一:可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为 1。
- 情况二:分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况 1 来计算。
- 判断一个左子树或者右子树是不是满二叉树:在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树,否则便不是。
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为 int 类型。
- 确定终止条件:判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归。
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加 1 是因为算上当前中间节点)就是目前节点为根节点的节点数量。
- 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
- 普通二叉树(递归):
-
复杂度
普通二叉树(递归) | 普通二叉树(迭代) | 完全二叉树(递归) | |
---|---|---|---|
时间复杂度 | O ( n ) O(n) O(n) | O ( n ) O(n) O(n) | O ( log n ∗ log n ) O(\log n*\log n) O(logn∗logn) |
空间复杂度 | O ( log n ) O(\log n) O(logn) | O ( n ) O(n) O(n) | O ( log n ) O(\log n) O(logn) |
- 普通二叉树(递归)
class Solution:
def countNodes(self, root: TreeNode) -> int:
return self.getNodesNum(root)
def getNodesNum(self, cur):
if not cur:
return 0
leftNum = self.getNodesNum(cur.left) #左
rightNum = self.getNodesNum(cur.right) #右
treeNum = leftNum + rightNum + 1 #中
return treeNum
#精简版
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
return 1 + self.countNodes(root.left) + self.countNodes(root.right)
- 普通二叉树(迭代)
import collections
class Solution:
def countNodes(self, root: TreeNode) -> int:
queue = collections.deque()
if root:
queue.append(root)
result = 0
while queue:
size = len(queue)
for i in range(size):
node = queue.popleft()
result += 1 #记录节点数量
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
- 完全二叉树(递归)
##写法1
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
left = root.left
right = root.right
leftDepth = 0 #这里初始为0是有目的的,为了下面求指数方便
rightDepth = 0
while left: #求左子树深度
left = left.left
leftDepth += 1
while right: #求右子树深度
right = right.right
rightDepth += 1
if leftDepth == rightDepth:
return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2,所以leftDepth初始为0
return self.countNodes(root.left) + self.countNodes(root.right) + 1
##写法2
class Solution: # 利用完全二叉树特性
def countNodes(self, root: 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 1+self.countNodes(root.left)+self.countNodes(root.right)
##写法3
class Solution: # 利用完全二叉树特性
def countNodes(self, root: TreeNode) -> int:
if not root: return 0
count = 0
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 1+self.countNodes(root.left)+self.countNodes(root.right)