题目 110.平衡二叉树
问题描述
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
解题思路![请添加图片描述](https://img-blog.csdnimg.cn/5d0ce42005b64812a57d08e40a1a329a.jpeg)
- 该代码通过递归地计算每个节点的高度,并比较左右子树的高度差来判断二叉树是否平衡。
- 如果任何节点的左右子树高度差大于1,或者任何子树不平衡,就返回-1表示不平衡;否则,返回对应的节点高度表示平衡。
- 判断根节点的高度是否为-1来确定整个二叉树是否平衡。
代码
- 递归法
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: TreeNode) -> bool:
return self.height(root) != -1
def height(self, node: TreeNode) -> int:
if not node:
return 0
left = self.height(node.left)
if left == -1:
return -1
right = self.height(node.right)
if right == -1 or abs(left - right) > 1:
return -1
return max(left, right) + 1
- 迭代
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
height_map = {}
stack = [root]
while stack:
node = stack.pop()
if node:
stack.append(node)
stack.append(None)
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
else:
real_node = stack.pop()
left, right = height_map.get(real_node.left, 0), height_map.get(real_node.right, 0)
if abs(left - right) > 1:
return False
height_map[real_node] = 1 + max(left, right)
return True
复杂度分析
-
时间复杂度O(n)
-
对于每个节点,get_height函数都会被调用一次,所以递归的时间复杂度为O(n),其中n是二叉树中的节点数。
在每个节点上,计算左子树和右子树的高度以及比较它们的高度差都需要O(1)的时间。 -
空间复杂度O(n)
-
递归调用会占用栈空间,其最大深度取决于二叉树的高度。在最坏情况下,如果二叉树是一个斜树(即只有左子树或只有右子树),则递归的最大深度为n。
在每个递归层级上,除了递归调用本身外,没有使用额外的空间。
因此,递归调用的空间复杂度为O(n)。
此外,代码中使用了常量空间来存储局部变量和返回值,不随输入规模变化,因此不会影响空间复杂度。
综上所述,整体的空间复杂度为O(n)。
题目 404.左叶子之和
问题描述
计算给定二叉树的所有左叶子之和。
解题思路
- 递归遍历二叉树的每个节点。
- 对于当前节点,如果它存在左子节点并且左子节点是叶子节点(即没有左右子节点),则将左子节点的值加到 res 中。
- 递归调用 sumOfLeftLeaves 函数分别在左子树和右子树上执行相同的操作。
- 返回最终的结果 res,即为二叉树中所有左叶子节点的值之和。
代码
- 递归
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
res = 0
if not root:
return 0
# Check if the left child is a left leaf
if root.left and not root.left.left and not root.left.right:
res += root.left.val
res += self.sumOfLeftLeaves(root.left)
res += self.sumOfLeftLeaves(root.right)
return res
- 迭代
class Solution:
def sumOfLeftLeaves(self, root: TreeNode) -> int:
"""
Idea: Each time check current node's left node.
If current node don't have one, skip it.
"""
stack = []
if root:
stack.append(root)
res = 0
while stack:
# 每次都把当前节点的左节点加进去.
cur_node = stack.pop()
if cur_node.left and not cur_node.left.left and not cur_node.left.right:
res += cur_node.left.val
if cur_node.left:
stack.append(cur_node.left)
if cur_node.right:
stack.append(cur_node.right)
return res
复杂度分析(递归)
-
时间复杂度O(n)
对于每个节点,我们需要判断它是否是左叶子节点,这需要O(1)的时间。
在每个节点上,我们递归调用sumOfLeftLeaves函数来计算左子树和右子树的左叶子节点值之和。
假设二叉树中有n个节点,每个节点最多被访问一次。
因此,总的时间复杂度为O(n)。 -
空间复杂度O(n)
-
递归调用会占用栈空间,其最大深度取决于二叉树的高度。在最坏情况下,如果二叉树是一个斜树(即只有左子树或只有右子树),递归的最大深度为n。
在每个递归层级上,除了递归调用本身外,没有使用额外的空间。
因此,递归调用的空间复杂度为O(n)。
此外,代码中使用了常量空间来存储局部变量和返回值,不随输入规模变化,因此不会影响空间复杂度。
综上所述,整体的空间复杂度为O(n)。
题目 257. 二叉树的所有路径
问题描述
给定一个二叉树,返回所有从根节点到叶子节点的路径。
解题思路
代码
- 递归
import copy
from typing import List, Optional
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root:
return []
result = []
self.generate_paths(root, [], result)
return result
def generate_paths(self, node: TreeNode, path: List[int], result: List[str]) -> None:
path.append(node.val)
if not node.left and not node.right:
result.append('->'.join(map(str, path)))
if node.left:
self.generate_paths(node.left, copy.copy(path), result)
if node.right:
self.generate_paths(node.right, copy.copy(path), result)
path.pop()
- 递归2
import copy
from typing import List, Optional
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root:
return []
result = []
self.generate_paths(root, [], result)
return result
def generate_paths(self, node: TreeNode, path: List[int], result: List[str]) -> None:
if not node:
return
path.append(node.val)
if not node.left and not node.right:
result.append('->'.join(map(str, path)))
else:
self.generate_paths(node.left, copy.copy(path), result)
self.generate_paths(node.right, copy.copy(path), result)
path.pop()
- 递归法+隐形回溯
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)
- 迭代法
class Solution:
"""二叉树的所有路径 迭代法"""
def binaryTreePaths(self, root: TreeNode) -> List[str]:
# 题目中节点数至少为1
stack, path_st, result = deque([root]), deque(), []
path_st.append(str(root.val))
while stack:
cur = stack.pop()
path = path_st.pop()
# 如果当前节点为叶子节点,添加路径到结果中
if not (cur.left or 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
复杂度分析
-
时间复杂度 O(n log(n))
对于每个节点,我们需要将其值添加到路径列表中,这需要 O(1) 的时间。
对于每个叶子节点,我们需要将路径列表转换为字符串形式,并将其添加到结果列表中。转换为字符串的过程需要 O(k) 的时间,其中 k 是路径的长度。
在最坏情况下,树是一个完全二叉树,共有 n/2 个叶子节点,每个叶子节点的路径长度最长为 log(n)。
因此,总的时间复杂度为 O(n log(n))。 -
空间复杂度O(n)
-
递归调用会占用栈空间,其最大深度取决于二叉树的高度。在最坏情况下,如果二叉树是一个链式结构(即只有左子树或只有右子树),递归的最大深度为 n。
在每个递归层级上,除了递归调用本身外,占用的额外空间包括路径列表 path 和结果列表 result。
路径列表的长度最大为树的高度,因此空间复杂度为 O(n)。
结果列表中保存了所有路径的字符串形式,最多有 n/2 个叶子节点,每个叶子节点的路径长度最长为 log(n)。
转换为字符串形式的过程中,额外空间的消耗取决于最长的路径长度,因此空间复杂度为 O(n log(n))。
综上所述,整体的空间复杂度为 O(n)。