110. 平衡二叉树
思路
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
求高度所以后序遍历。
递归三部曲分析:
- 明确递归函数的参数和返回值:
参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。 - 明确终止条件
递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0 - 明确单层递归的逻辑
判断左右子树高度差值是否小于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: Optional[TreeNode]) -> bool:
return self.helper(root) != -1
def helper(self, root):
if not root:
return 0
left = self.helper(root.left) # 左
right = self.helper(root.right) # 右
if left == -1 or right == -1 or abs(left - right) > 1:
return -1
else:
return 1 + max(left, right) # 中
257. 二叉树的所有路径
思路
这题要求从根节点到叶子的路径,所以使用前序遍历。
前序遍历以及回溯的过程如图:
代码
递归加回溯
# 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 binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
result = []
self.traversal(root, [], result)
return result
def traversal(self, cur, path, result):
path.append(cur.val) # 中
if not cur.left and not cur.right:
result.append("->".join(map(str, path)))
return
# 左
if cur.left:
self.traversal(cur.left, path, result)
path.pop()# 回溯
# 右
if cur.right:
self.traversal(cur.right, path, result)
path.pop() #回溯
递归+隐形回溯
# 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
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)
总结
-
隐形回溯中,对于path的赋值可以是
path[:]
。这样子赋值了之后,就不需要再进行额外的操作来进行回溯(对于上面的例子,就是path.pop()
),因为path[:]
本质上是一个浅复制,对每一次递归,都传进去一个复制的path
,对于path
的修改不影响上一层级的path
。 -
浅复制和深复制
-
浅复制
- 只复制复合对象本身,不复制其中的子对象。复制之后的对象和原来的对象内存地址不一样。
- 如果原始对象包含了对其他对象的引用,浅复制会复制这些引用,而不是引用的内容。所以,原始对象和其浅复制引用的子对象是同一个。
-
深复制
- 复制对象及其所有嵌套的子对象。
- 生成的复制是完全独立的,修改复制对象不会影响原始对象。
-
例子
path = [1, 2, 3, 4] copied_path = path[:] copied_path[0] = 99 print(path) # This will print [1, 2, 3, 4] print(copied_path) # This will print [99, 2, 3, 4]
浅复制不会复制子对象
path = [[1, 2], [3, 4]] copied_path = path[:] copied_path[0][0] = 99 print(path) # This will print [[99, 2], [3, 4]] print(copied_path) # This will print [[99, 2], [3, 4]]
-
-
map()
的用法。map(str, path)
相当于
for i in range(len(path)):
path[i] = str(path[i])
404.左叶子之和
思路
判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。
代码
# 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: Optional[TreeNode]) -> int:
if not root:
return 0
if not root.left and not root.right:
return 0
left = self.sumOfLeftLeaves(root.left)
# 下面这一行代码需要在 left = self.sumOfLeftLeaves(root.left)的后面
# 因为如果我们找到一个这样的左子叶,那就不需要再往左孩子递归了
# 如果我们颠倒了顺序,那我们永远找不到符合条件的左子叶
# 因为只要left一被root.left.val赋值
# 接下来就马上被self.sumOfLeftLeaves(root.left)的值给覆盖
if not root.left.left and not root.left.right and root.left:
left = root.left.val
right = self.sumOfLeftLeaves(root.right)
total = left + right
return total