目录
十六、剑指 Offer 64. 求 1 + 2 + … + n
十七、剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
十八、剑指 Offer 68 - II. 二叉树的最近公共祖先
十、剑指 Offer 36. 二叉搜索树与双向链表
10.1 题求
10.2 求解
法一:中序遍历
- 空间复杂度
- 时间复杂度
# 32ms - 98.66%
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:
return
# 哨兵节点 x2
dummy = prev = TreeNode(0)
def inorder(node):
nonlocal prev # 使用非局部作用域的 prev
if node:
inorder(node.left)
prev.right = node # ->
node.left = prev # <-
prev = node # update
inorder(node.right)
return
# 中序遍历互连
inorder(root)
# 尾部节点跳过哨兵节点与头部节点互联
prev.right = dummy.right
dummy.right.left = prev
# 返回头部节点
return prev.right
官方说明
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:
return
def dfs(cur):
if not cur:
return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
10.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dbies/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dj09d/
十一、剑指 Offer 37. 序列化二叉树
11.1 题求
11.2 求解
# 一些测试用例
[5, null, 2 ,1]
[5,2,3,null,null,2,4,3,1]
[]
[1,9,2,8,10]
[-10, -5, 0, 12, -3]
[-1,0,1]
[-1, -2, -3, 0, 4]
[1, 2]
[1,2,3,null,null,4,5]
[]
[1]
[1, 2, 3]
官方说明
# 104ms - 94.76%
class Codec:
def serialize(self, root):
if not root:
return "[]"
queue = collections.deque()
queue.append(root)
res = []
while queue:
node = queue.popleft()
# 有效节点
if node:
res.append(str(node.val))
queue.append(node.left)
queue.append(node.right)
# 空节点
else:
res.append("null")
# ',' 分隔各节点
return '[' + ','.join(res) + ']'
def deserialize(self, data):
if data == "[]":
return
vals, i = data[1:-1].split(','), 1
root = TreeNode(int(vals[0]))
queue = collections.deque() # i = 0
queue.append(root)
while queue:
# 当前节点
node = queue.popleft()
# 左子节点
if vals[i] != "null":
node.left = TreeNode(int(vals[i]))
queue.append(node.left)
i += 1 # 为当前节点移位 (若空节点则仅移位不处理)
# 右子节点
if vals[i] != "null":
node.right = TreeNode(int(vals[i]))
queue.append(node.right)
i += 1 # 为当前节点移位 (若空节点则仅移位不处理)
return root
11.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/990pf2/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/997ebc/
十二、剑指 Offer 38. 字符串的排列
12.1 题求
12.2 求解
法一:回溯
# 30%
class Solution:
def permutation(self, s: str) -> List[str]:
# 字符串索引集合
index_set = set(i for i in range(len(s)))
# 字符串排列结果集合
res = set()
def traverse(have, rest):
# 长度满足, 记录返回
if not rest:
res.add(''.join(have))
return
# 遍历其余元素的索引
select = rest.copy()
for i in select:
# 加入
have.append(s[i])
rest.remove(i)
# 回溯
traverse(have, rest)
# 还原
have.pop()
rest.add(i)
traverse([], index_set)
return list(res)
官方说明
# 88ms - 94.85%
class Solution:
def permutation(self, s: str) -> List[str]:
c, res = list(s), []
def dfs(x):
if x == len(c)-1:
res.append(''.join(c)) # 添加排列方案
return
dic = set() # 当前位 (index = x) 已尝试索引集合
for i in range(x, len(c)): # 当前位 (index = x) 开始依次交换后续位置
if c[i] in dic:
continue # 当前位 (index = x) 已重复 - 剪枝
dic.add(c[i]) # 当前位 (index = x) 未重复 - 记录
c[i], c[x] = c[x], c[i] # 交换, 将 c[i] 设为第 x 位
dfs(x+1) # 固定第 x+1 位字符
c[i], c[x] = c[x], c[i] # 恢复, 将 c[x] 换回第 i 位
dfs(0)
return res
12.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dfv5h/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/50hah3/
十三、剑指 Offer 54. 二叉搜索树的第 k 大节点
13.1 题求
13.2 求解
法一:递归 - 逆中序遍历
# 52ms - 90.65%
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(node):
''' 逆中序遍历 - 从大到小排列 '''
if node:
dfs(node.right)
inorder.append(node.val)
dfs(node.left)
inorder = []
dfs(root)
return inorder[k-1]
法二:迭代 - 逆中序遍历
# 44ms - 98.96%
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
'''
中序遍历迭代方式
https://blog.csdn.net/qq_39478403/article/details/107329233
'''
inv_inorder = [] # 逆中序遍历 - 从大到小排列
stack = []
node = root
while stack or node:
if node:
stack.append(node)
node = node.right # 逆中序遍历
else:
node = stack.pop()
inv_inorder.append(node.val)
node = node.left # 逆中序遍历
# 一旦达到指定数目, 即为需要返回的第 k 大值
if len(inv_inorder) == k:
return inv_inorder[-1]
官方说明
# 36ms - 99.93%
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(root):
if not root:
return
dfs(root.right) # 中序遍历的倒序
if self.k == 0: # 到达指定位置, 返回
return
self.k -= 1 # 否则待查询项 -1
if self.k == 0: # 若到达指定位置
self.res = root.val # 取得指定节点值
dfs(root.left) # 中序遍历的倒序
self.k = k # 设为类属性
dfs(root) # DFS
return self.res # 返回指定节点值
13.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58df23/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/580cam/
十四、剑指 Offer 55 - I. 二叉树的深度
14.1 题求
14.2 求解
法一:递归 - DFS
# 40ms - 94.44%
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
法二:迭代 - BFS
# 40ms - 94.44%
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
depth = 0
deque = collections.deque()
deque.append(root)
while deque:
for _ in range(len(deque)):
node = deque.popleft()
if node.left:
deque.append(node.left)
if node.right:
deque.append(node.right)
depth += 1
return depth
官方说明
14.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hgr5i/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hz9xe/
十五、剑指 Offer 55 - II. 平衡二叉树
15.1 题求
15.2 求解
法一:DFS - 后序遍历
# 36ms - 99.73%
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
max_diff = 0 # 整树的最大深度差
def dfs(node, depth):
nonlocal max_diff
# 返回根节点的整树(全局)深度
if not node:
return depth
# 计算当前节点的左右子树的最大深度
left_depth = dfs(node.left, depth+1)
right_depth = dfs(node.right, depth+1)
# 计算当前节点的左右子树的最大深度差、更新整树的最大深度差
diff = abs(left_depth - right_depth)
if diff > max_diff:
max_diff = diff
# 返回当前节点左右子树中的最大深度
return max(left_depth, right_depth)
dfs(root, 0)
return max_diff < 2
官方说明
# 44ms - 97.44%
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def recur(root):
if not root:
return 0
left = recur(root.left) # 左子树最大深度
if left == -1:
return -1
right = recur(root.right) # 右子树最大深度
if right == -1:
return -1
# 一旦出现左右子树深度差 > 1 则永远返回 -1
return max(left, right) + 1 if abs(left - right) <= 1 else -1
return recur(root) != -1
# 56ms - 77.74%
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
return abs(self.depth(root.left) - self.depth(root.right)) <= 1 and \
self.isBalanced(root.left) and self.isBalanced(root.right)
def depth(self, root):
if not root:
return 0
return max(self.depth(root.left), self.depth(root.right)) + 1
15.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hzffg/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hscjv/
十六、剑指 Offer 64. 求 1 + 2 + … + n
16.1 题求
16.2 求解
法一:递归 (短路特性)
# 40ms - 85.18%
class Solution:
def sumNums(self, n: int) -> int:
'''
同时, 利用 bool 值是 int 子类和 and 的短路特性
其成立时将返回最后一个表达式, n=0 时则将终止并返回 0
'''
return n and (n + self.sumNums(n-1))
官方说明
# 36ms - 94.42%
class Solution:
def __init__(self):
self.res = 0
def sumNums(self, n: int) -> int:
(n > 1) and self.sumNums(n-1) # n 的大小决定了递归是否继续进行
self.res += n # 累加当前值 n
return self.res # 返回当前累加和
16.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9h44cj/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9h1gyt/
十七、剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
17.1 题求
17.2 求解
法一:DFS + 剪枝
根据题求及 二叉平衡树 性质 (根节点必大于左子节点、必小于右子节点),当前节点 node 仅存在 3 种情况:
- min(p.val, q.val) ≤ node.val ≤ max(p.val, q.val),此时当前节点 node 必为最近公共祖先
- node.val < min(p.val, q.val),此时当前节点 node 需要向右子树搜索 dfs(node.right)
- node.val > max(p.val, q.val),此时当前节点 node 需要向左子树搜索 dfs(node.left)
# 56ms - 99.97%
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 二叉搜索树的最佳最近公共祖先节点
ancester = None
min_val = min(p.val, q.val)
max_val = max(p.val, q.val)
def dfs(node):
nonlocal ancester # 采用外层作用域变量
if not node: # 找到根节点外了 - 始终未找到
return False
elif ancester is not None: # DFS 剪枝 - 已找到就不要再继续找了
return True
# 情况 1
if min_val <= node.val and node.val <= max_val:
ancester = node
# 情况 2
elif min_val > node.val:
dfs(node.right)
# 情况 3
elif node.val > max_val:
dfs(node.left)
dfs(root)
return ancester
官方说明
# 76ms - 90.67%
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while root:
if root.val < p.val and root.val < q.val: # p,q 都在 root 的右子树中
root = root.right # 遍历至右子节点
elif root.val > p.val and root.val > q.val: # p,q 都在 root 的左子树中
root = root.left # 遍历至左子节点
else: break
return root
# 72ms - 94.75%
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if p.val > q.val: p, q = q, p # 保证 p.val < q.val
while root:
if root.val < p.val: # p,q 都在 root 的右子树中
root = root.right # 遍历至右子节点
elif root.val > q.val: # p,q 都在 root 的左子树中
root = root.left # 遍历至左子节点
else: break
return root
# 76ms - 90.67% - 未经剪枝性能还是差了点
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
return root
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/575kd2/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5793vc/
十八、剑指 Offer 68 - II. 二叉树的最近公共祖先
18.1 题求
18.2 求解
法一:DFS + 后序遍历
# 68ms - 90.46%
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
# 找到根节点外都没找到 或 找到指定节点, 都返回之
if not root or root.val == p.val or root.val == q.val:
return root
# DFS 寻找指定节点是否存在于左、右子树
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
# 指定节点均不存在
if not left and not right:
return None
# 指定节点只存在其一
elif not right:
return left
elif not left:
return right
# 指定节点均存在, 找到二叉树的最佳公共祖先
else:
return root
官方说明
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left:
return right
if not right:
return left
return root
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left and not right: # 1.
return
if not left: # 3.
return right
if not right: # 4.
return left
return root # 2. if left and right:
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57euni/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57o72e/