目录
六、剑指 Offer 32 - I. 从上到下打印二叉树 I
七、剑指 Offer 32 - II. 从上到下打印二叉树 II
八、剑指 Offer 32 - III. 从上到下打印二叉树 III
序、回溯法
一、剑指 Offer 12. 矩阵中的路径
1.1 题求
# 部分测试用例
[["A","B","C","E"],["S","F","E","S"],["A","D","E","E"]]
"ABCESEEEFS"
[["a","b"]]
"ba"
[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]]
"ABCCED"
[["a","b"],["c","d"]]
"abcd"
[["a"]]
"a"
[["a"]]
"b"
[["a","a"]]
"aaa"
1.2 求解
法一:深度优先搜索
- 时间复杂度 ,最差情况下,需要遍历矩阵中长度为 字符串的所有方案,时间复杂度为 ;矩阵中共有 个起点,时间复杂度为 。方案数计算: 设字符串 长度为 ,搜索中每个字符有上、下、左、右四方向可选,舍弃回头(来时的字符)的方向,剩下 3 种选择,因此方案数的复杂度为 。
- 空间复杂度 ,其中 为字符串长度。搜索过程中的递归深度不超过 ,因此系统因函数调用累计使用的栈空间占用 ,(因为函数返回后,系统调用的栈空间会释放)。最坏情况下 ,此时系统栈使用 的额外空间。
# 172ms - 95.06%
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m = len(board) # 行数
n = len(board[0]) # 列数
w = len(word) # 目标字符数
def dfs(seen, row, col, idx):
# 目标 idx 越界 - 所有目标已完成,
if idx == w:
return True
# 当前坐标越界 / 当前坐标已走过 / 当前 word 不符 - 退出
if (row < 0) or (row >= m) or (col < 0) or (col >= n) or\
((row, col) in seen) or (board[row][col] != word[idx]):
return False
# 当前坐标加入已走过坐标集合
seen.add((row, col))
# 寻找下一个目标 idx
idx += 1
# 是否找到合适的下一个坐标的搜索结果
find = dfs(seen, row+1, col, idx) or dfs(seen, row-1, col, idx) or\
dfs(seen, row, col+1, idx) or dfs(seen, row, col-1, idx)
# 若无法找到合适的下一个坐标, 则不走当前坐标, 并将其移出已走过坐标集合
if not find:
seen.remove((row, col))
# 返回搜索结果
return find
# 遍历各个起点
for i in range(m):
for j in range(n):
if dfs(set(), i, j, 0):
return True # 但凡找到一条可行路径, 返回
return False
官方说明
### 通过原地修改矩阵元素值来标记已访问/走过, 比使用 seen/visit 等集合更高效和节省
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]:
return False
if k == len(word) - 1:
return True
board[i][j] = '' # 代表此元素已访问过,防止之后搜索时重复访问
res = dfs(i + 1, j, k + 1) or dfs(i - 1, j, k + 1) or\
dfs(i, j + 1, k + 1) or dfs(i, j - 1, k + 1)
board[i][j] = word[k] # 还原当前矩阵元素
return res
for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0): return True
return False
1.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58wowd/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58d5vh/
二、剑指 Offer 13. 机器人的运动范围
2.1 题求
2.2 求解
法一:线性搜索 - 近似广度优先搜索
- 空间复杂度
- 时间复杂度 ,其中 表示可达格子总数
# 36ms - 99.54%
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
res = 0 # 可达格子总数
prev_limit = [False for _ in range(n)] # 上一行
curr_limit = [False for _ in range(n)] # 当前行
prev_limit[0] = True # 初始位置 (0, 0) 必然可达
# 计算行坐标和列坐标的数位之和 - 不要转换为 str 再求和, 效率非常低!!!
def sum_k(place):
x, y = place
sx = sy = 0
while x:
sx += x % 10 # 个位
x //= 10 # 移位
while y:
sy += y % 10 # 个位
y //= 10 # 移位
return sx + sy
# 遍历行
for x in range(m):
if not any(prev_limit): # 早停:若上一行没有任何位置可达, 提前结束循环
break
# 遍历列
for y in range(n):
# (x, y) 可达
if sum_k((x, y)) <= k and (curr_limit[y-1] or prev_limit[y]):
curr_limit[y] = True
res += 1
# 更新
prev_limit = curr_limit
curr_limit = [False for _ in range(n)]
return res
法二:深度优先搜索
- 空间复杂度 ,其中 表示可达格子总数
- 时间复杂度
# 48 ms - 89.82%
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
# 已走过坐标集合
visited = set()
# 计算行坐标和列坐标的数位之和 - 不要转换为 str 再求和, 效率非常低!!!
def sum_k(place):
x, y = place
sx = sy = 0
while x:
sx += x % 10 # 个位
x //= 10 # 移位
while y:
sy += y % 10 # 个位
y //= 10 # 移位
return sx + sy
# 深度优先搜索
def dfs(x, y):
# 坐标不符合数值要求 - 可行性剪枝
if (x >= m) or (y >= n) or (sum_k((x, y)) > k) or ((x, y) in visited):
return len(visited)
# 记录当前坐标表示已走过
visited.add((x, y))
# 只需向右和向下, 而无需向四周扩散!
dfs(x+1, y)
dfs(x, y+1)
# 返回当前已走过的长度
return len(visited)
return dfs(0, 0)
官方说明
def sums(x):
s = 0
while x != 0:
s += x % 10
x = x // 10
return s
s_x + 1 if (x + 1) % 10 else s_x - 8
# 40ms - 98.40% - 比自写的更好 使用了线性递归
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
# 行列索引 i 和 j ,两者的数位和 si, sj
def dfs(i, j, si, sj):
# 不符合要求的坐标
if i >= m or j >= n or k < si + sj or (i, j) in visited:
return 0
# 当前坐标已探索
visited.add((i,j))
# 仅向右和向下移动 - 只有当 (9+1) % 10 == 0 所以直接令 9 -> 9-8=1 相当于循环 (10 和为 1)
return 1 + dfs(i+1, j, si+1 if (i+1) % 10 else si-8, sj) +\ # 向下
dfs(i, j+1, si, sj+1 if (j+1) % 10 else sj-8) # 向右
visited = set()
return dfs(0, 0, 0, 0)
# 36ms - 99.54%
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
queue, visited = [(0, 0, 0, 0)], set()
# 行列索引 i 和 j ,两者的数位和 si, sj
while queue:
i, j, si, sj = queue.pop(0)
# 不符合要求的坐标
if i >= m or j >= n or k < si+sj or (i, j) in visited:
continue
# 当前坐标已探索
visited.add((i,j))
# 仅向右和向下移动 - 只有当 (9+1) % 10 == 0 所以直接令 9 -> 9-8=1 相当于循环 (10 和为 1)
queue.append((i+1, j, si+1 if (i+1) % 10 else si-8, sj))
queue.append((i, j+1, si, sj+1 if (j+1) % 10 else sj-8))
return len(visited)
2.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9h6vo2/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hka9c/
三、剑指 Offer 26. 树的子结构
3.1 题求
3.2 求解
# 测试用例
[10,12,6,8,3,11]
[10,12,6,8]
[1,2,3,4]
[3]
[1,2,3]
[]
[]
[2,3,2,1]
[3,null,2,2]
[]
[1,2]
[2]
[1,0,1,-4,-3]
[1,-4]
法一:BFS + DFS
- 空间复杂度 ,其中 为 A 树的节点数
- 时间复杂度 ,其中 为 B 树的节点数,最差情况每个 A 节点都要匹配一次。
# 84ms - 99.02%
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
# 任一为空都不可
if not B or not A:
return False
# DFS 匹配
def match(node_a, node_b):
# B 耗尽了都没发现不一致 (要以 B 的结束为准)
if not node_b:
return True
# B 没耗尽, A 先耗尽了 或 A 与之不一致
elif not node_a or node_b.val != node_a.val:
return False
# 左右子节点继续配对
return match(node_a.left, node_b.left) and match(node_a.right, node_b.right)
# BFS 搜索
deque = collections.deque()
deque.append(A)
while deque:
for _ in range(len(deque)):
# 当前节点
node = deque.popleft()
# 孩子节点
if node.left:
deque.append(node.left)
if node.right:
deque.append(node.right)
# 数值匹配
if node.val == B.val and match(node, B):
return True
return False
官方说明
# 116ms - 69.32%
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def recur(A, B):
if not B:
return True
if not A or A.val != B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right)
return bool(A and B) and (recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B))
3.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dshwe/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dsbng/
四、剑指 Offer 27. 二叉树的镜像
4.1 题求
4.2 求解
法一:BFS
- 空间复杂度
- 时间复杂度
# 36ms - 82.02%
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
# 原生
stack_lhs = [root]
# 镜像
head_rhs = TreeNode(root.val)
stack_rhs = [head_rhs]
# BFS
while stack_lhs:
for _ in range(len(stack_lhs)):
node_lhs = stack_lhs.pop()
node_rhs = stack_rhs.pop()
# 从右
if node_lhs.right:
stack_lhs.append(node_lhs.right)
node_rhs.left = TreeNode(node_lhs.right.val) # copy right child as left
stack_rhs.append(node_rhs.left)
# 往左
if node_lhs.left:
stack_lhs.append(node_lhs.left)
node_rhs.right = TreeNode(node_lhs.left.val) # copy left child as right
stack_rhs.append(node_rhs.right)
return head_rhs
官方说明
# 32ms - 93.22%
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
# 空节点
if not root:
return
# 交换当前节点的左右子节点
root.left, root.right = self.mirrorTree(root.right), self.mirrorTree(root.left)
# 返回当前节点
return root
# 32ms - 93.22%
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root:
return
stack = [root]
while stack:
node = stack.pop()
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
node.left, node.right = node.right, node.left # 交换
return root
4.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/59zt5i/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/59slxe/
五、剑指 Offer 28. 对称的二叉树
5.1 题求
5.2 求解
法一:正常前序遍历 (根-左-右) + 镜像前序遍历 (根-右-左)
- 空间复杂度
- 时间复杂度
# 28ms - 99.26%
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
def recur(lhs, rhs):
# 始终保持对称至叶子节点外
if not lhs and not rhs:
return True
# 出现不对称
if (not lhs) or (not rhs) or (lhs.val != rhs.val):
return False
# 匹配子节点 - lhs 是正常前序遍历 - rhs 是镜像前序遍历
return recur(lhs.left, rhs.right) and recur(lhs.right, rhs.left)
return recur(root.left, root.right)
官方说明
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def recur(L, R):
if not L and not R:
return True
if not L or not R or L.val != R.val:
return False
return recur(L.left, R.right) and recur(L.right, R.left)
return not root or recur(root.left, root.right)
5.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5d412v/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5d1zmj/
六、剑指 Offer 32 - I. 从上到下打印二叉树 I
6.1 题求
6.2 求解
法一:BFS
- 空间复杂度
- 时间复杂度
# 28ms - 98.76%
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root:
return []
res = []
deque = collections.deque()
deque.append(root)
while deque:
for _ in range(len(deque)):
node = deque.popleft()
res.append(node.val)
if node.left:
deque.append(node.left)
if node.right:
deque.append(node.right)
return res
官方说明
# Python
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root:
return []
res, queue = [], collections.deque()
queue.append(root)
while queue:
node = queue.popleft()
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
// C++
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
if(!root)
return res;
TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
res.push_back(node->val);
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
return res;
}
};
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9ackoe/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9ab39g/
七、剑指 Offer 32 - II. 从上到下打印二叉树 II
7.1 题求
7.2 求解
法一:BFS
- 空间复杂度
- 时间复杂度
# 28ms - 98.84%
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = []
deque = collections.deque()
deque.append(root)
while deque:
layer = []
for _ in range(len(deque)):
node = deque.popleft()
layer.append(node.val)
if node.left:
deque.append(node.left)
if node.right:
deque.append(node.right)
res.append(layer)
return res
官方说明
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res, queue = [], collections.deque()
queue.append(root)
while queue:
tmp = []
for _ in range(len(queue)):
node = queue.popleft()
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(tmp)
return res
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vawr3/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v22om/
八、剑指 Offer 32 - III. 从上到下打印二叉树 III
8.1 题求
8.2 求解
法一:BFS + 双栈
- 空间复杂度
- 时间复杂度
# 20ms - 100%
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = [] # 结果列表
stack_lr = [root] # 偶数层用 stack_lr
stack_rl = [] # 奇数层用 stack_rl
layer_idx = 0 # 层数
while stack_lr or stack_rl:
layer = [] # 初始化当前层节点值列表
# 偶数层用 stack_lr ->
if layer_idx % 2 == 0:
for _ in range(len(stack_lr)):
node = stack_lr.pop()
layer.append(node.val)
if node.left:
stack_rl.append(node.left)
if node.right:
stack_rl.append(node.right)
# 奇数层用 stack_rl <-
else:
for _ in range(len(stack_rl)):
node = stack_rl.pop()
layer.append(node.val)
if node.right:
stack_lr.append(node.right)
if node.left:
stack_lr.append(node.left)
res.append(layer) # 记录当前层节点值列表
layer_idx += 1 # 层数 + 1
return res
官方说明
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res, deque = [], collections.deque([root])
while deque:
tmp = collections.deque()
for _ in range(len(deque)):
node = deque.popleft()
if len(res) % 2 == 0:
tmp.append(node.val) # 奇数层 -> 插入队列尾部
else:
tmp.appendleft(node.val) # 偶数层 -> 插入队列头部
if node.left:
deque.append(node.left)
if node.right:
deque.append(node.right)
res.append(list(tmp))
return res
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
res, deque = [], collections.deque()
deque.append(root)
while deque:
tmp = []
# 打印奇数层
for _ in range(len(deque)):
# 从左向右打印
node = deque.popleft()
tmp.append(node.val)
# 先左后右加入下层节点
if node.left: deque.append(node.left)
if node.right: deque.append(node.right)
res.append(tmp)
if not deque: break # 若为空则提前跳出
# 打印偶数层
tmp = []
for _ in range(len(deque)):
# 从右向左打印
node = deque.pop()
tmp.append(node.val)
# 先右后左加入下层节点
if node.right: deque.appendleft(node.right)
if node.left: deque.appendleft(node.left)
res.append(tmp)
return res
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res, queue = [], collections.deque()
queue.append(root)
while queue:
tmp = []
for _ in range(len(queue)):
node = queue.popleft()
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(tmp[::-1] if len(res) % 2 else tmp)
return res
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vnp91/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vve57/
九、剑指 Offer 34. 二叉树中和为某一值的路径
9.1 题求
9.2 求解
法一:DFS
- 空间复杂度
- 时间复杂度
# 36ms - 98.28#%
class Solution:
def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
if not root:
return []
def dfs(node, summ, path):
path = path.copy() # 浅拷贝路径
summ -= node.val # 残差
path.append(node.val) # 当前节点值加入路径
# 叶子节点
if not node.left and not node.right:
if summ == 0:
res.append(path)
return
# 左子节点
if node.left:
dfs(node.left, summ, path)
# 右子节点
if node.right:
dfs(node.right, summ, path)
return
res = []
dfs(root, target, [])
return res
官方说明
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
res, path = [], []
def recur(root, tar):
if not root:
return
path.append(root.val) # 关键一步 - 回溯
tar -= root.val
if tar == 0 and not root.left and not root.right:
res.append(list(path)) # 相当于浅拷贝
recur(root.left, tar)
recur(root.right, tar)
path.pop() # 关键一步 - 回溯
recur(root, sum)
return res
9.3 解说
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dy6pt/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dc8rr/