结构转换
BST的重要性质:
- BST的中序遍历结果是一个sorted array BST的2个解题技巧:
- DFS时不断更新前驱节点
prev,满足prev.val < cur.val- 传递BST的上下界
low, high,控制BST的构建/验证
428. 序列化和反序列化 N 叉树(Hard)
Solu:
- 在
root.val之后,加入一位标识len(root.children),以确定需要loop多少次deserialize来构建所有children - 为了只在数字之间添加
',',可以先把所有数字装进一个list,再转化为str
Code:
class Codec:
def serialize(self, root: 'Node') -> str:
def dfs(node):
if node:
res.append(str(node.val))
res.append(str(len(node.children)))
for child in node.children:
dfs(child)
res = []
dfs(root)
return ','.join(res) if res else ''
def deserialize(self, data: str) -> 'Node':
def dfs(list_data):
root_val = list_data.pop(0)
root = Node(root_val, [])
num_children = int(list_data.pop(0))
for _ in range(num_children):
root.children.append(dfs(list_data))
return root
if not data:
return None
return dfs(data.split(','))
449. 序列化和反序列化二叉搜索树(Medium)
Solu:
- 因为题目要求编码的字符串应尽可能紧凑,所以不可以用
'#'来标识null节点 - 一旦
list[0]超出边界,则说明当前正在构建的BST子树已经构建完了,可以去构建下一个BST子树
Code:
class Codec:
def serialize(self, root: TreeNode) -> str:
res = '' if root is None else str(root.val)
if not root:
return res
if root.left:
res += ',' + self.serialize(root.left)
if root.right:
res += ',' + self.serialize(root.right)
return res
def deserialize(self, data: str) -> TreeNode:
def dfs(list_data, low, high):
if not list_data:
return None
val = list_data[0]
if int(val) < low or int(val) > high:
return None
node = TreeNode(int(list_data.pop(0)))
node.left = dfs(list_data, low, int(val))
node.right = dfs(list_data, int(val), high)
return node
return dfs(data.split(','), -sys.maxsize, sys.maxsize) if data else None
1008. 前序遍历构造二叉搜索树(Medium)
Solu:
- 上下边界
low和high控制:一旦preorder[0]超界,则说明当前BST子树构建完了,可以去构建下一棵BST子树
Code:
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
def dfs(low, high):
if not preorder or preorder[0] < low or preorder[0] > high:
return None
root = TreeNode(preorder.pop(0))
root.left = dfs(low, root.val)
root.right = dfs(root.val, high)
return root
return dfs(float('-inf'), float('inf'))
426. 将二叉搜索树转化为排序的双向链表(Medium)
Solu:
- 双端链表的
head节点是BST的最左leaf node - DFS中序遍历时,不断更新前驱
prev,将prev和curNode做链接 - 勿忘:将双端列表的首尾链接
Code:
class Solution:
def __init__(self):
self.head = None # 双端链表的头节点为tree的最左leaf node
self.prev = None # 记录前驱节点
def treeToDoublyList(self, root: 'Optional[Node]') -> 'Optional[Node]':
def dfs(node): # in-order traversal
if not node:
return
dfs(node.left)
if not self.head:
self.head = node
if self.prev:
self.prev.right = node
node.left = self.prev
self.prev = node
dfs(node.right)
dfs(root)
if self.head: # 调整双端链表的首尾
self.head.left = self.prev
self.prev.right = self.head
return self.head
BST
270. 最接近的二叉搜索树值(Easy)
Solu:
- if
target == root.val, thenans = root必然成立 - if
target < root.val, thenans = root(上界) OR 左子树中最接近target的node(下界) - if
target > root.val, thenans = root(下界) OR 右子树中最接近target的node(上界)
Code:
class Solution:
def closestValue(self, root: Optional[TreeNode], target: float) -> int:
if not root:
return sys.maxsize
if root.val == target:
return root.val
if target > root.val:
right = self.closestValue(root.right, target)
return right if abs(right - target) < abs(root.val - target) else root.val
else:
left = self.closestValue(root.left, target)
return left if abs(left - target) < abs(root.val - target) else root.val
450. 删除二叉搜索树中的节点(Medium)
Solu:
- 对于待删除的node,用该node的 左子树的最大值 OR 右子树的最小值 来填补
Code:
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
def findMin(node) -> int:
while node.left:
node = node.left
return node.val
if not root:
return None
if root.val < key:
root.right = self.deleteNode(root.right, key)
elif root.val > key:
root.left = self.deleteNode(root.left, key)
else:
if not root.left:
return root.right
if not root.right:
return root.left
root.val = findMin(root.right) # replace root with min value in right subtree
root.right = self.deleteNode(root.right, root.val) # delete min value in right subtree
return root
98. 验证二叉搜索树(Medium)
Solu 1:传入上下边界
- if
root.val <= lower or root.val >= upper,则验证失败;否则,再分别验证左右子树
Code 1:
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def recursion(lower, upper, node):
if node is None:
return True
val = node.val
if val <= lower or val >= upper:
return False
return recursion(lower, val, node.left) and recursion(val, upper, node.right)
return recursion(float('-inf'), float('inf'), root)
Solu 2:中序遍历 + 记录前驱prev
- 中序遍历,结果是sorted array
- 验证的同时,不断更新
prev,一旦prev.val >= cur.val,则验证失败
class Solution:
def __init__(self):
self.prev = None
def isValidBST(self, root: TreeNode) -> bool:
def dfs(node):
if not node:
return True
if not dfs(node.left):
return False
if self.prev and self.prev.val >= node.val:
return False
self.prev = node
return dfs(node.right)
return dfs(root)
173. 二叉搜索树迭代器(Meidum)
Solu:stack迭代式中序遍历
- 利用iterative的形式(stack)对BST进行中序遍历
Code:
class BSTIterator:
def __init__(self, root: TreeNode):
self.stack = []
self.pushAllLeft(root)
def next(self) -> int:
node = self.stack.pop()
self.pushAllLeft(node.right)
return node.val
def hasNext(self) -> bool:
return len(self.stack) > 0
def pushAllLeft(self, root):
while root:
self.stack.append(root)
root = root.left
99. 恢复二叉搜索树(Medium)
Solu:
- 只需要按照inorder去遍历,发现2个顺序出问题的node交换即可
- 只保留第一次出现的
first和 最后一次出现的second,并对其进行swap
- 只保留第一次出现的
Code:
class Solution:
def __init__(self):
self.prev = TreeNode(float('-inf'))
self.first = None
self.second = None
def recoverTree(self, root: Optional[TreeNode]) -> None:
def inorder(node) -> None:
if not node:
return
inorder(node.left)
if self.prev.val >= node.val:
if not self.first:
self.first = self.prev
self.second = node
self.prev = node
inorder(node.right)
inorder(root)
self.first.val, self.second.val = self.second.val, self.first.val
Solu:
LC108 的套娃题
- 中序遍历得到排序
- 按照排序从中间开始构建树,左右平衡,深度差异不会大于1
Code:
class Solution:
def balanceBST(self, root: TreeNode) -> TreeNode:
def inorder(node, res):
if node:
inorder(node.left, res)
res.append(node.val)
inorder(node.right, res)
def construct(res, l, r):
if l > r:
return None
mid = (l + r) // 2
node = TreeNode(res[mid])
node.left = construct(res, l, mid - 1)
node.right = construct(res, mid + 1, r)
return node
res = []
inorder(root, res)
return construct(res, 0, len(res) - 1)
96. 不同的二叉搜索树(Medium)
Solu 1:记忆化DFS
- 一个BST有多少种排序方式只和 左右子树各自的#nodes 相关(和node的
val无关) numTrees(n) = sum(numsTrees(i-1) * numTrees(n-i)) (1 ≤ i ≤ n)
Code 1:
class Solution:
def __init__(self):
self.memo = {}
def numTrees(self, n: int) -> int:
if n <= 1:
return 1
if n in self.memo:
return self.memo[n]
res = 0
for i in range(1, n + 1):
res += self.numTrees(i - 1) * self.numTrees(n - i)
self.memo[n] = res
return res
Solu 2:DP
dp[i]= i个nodes可以组成多少种不同的BSTdp[i] = sum(dp[k-1] * dp[i-k]) (1 ≤ k ≤ i)
Code 2:
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0] = 1
dp[1] = 1
for i in range(2, n + 1):
for j in range(1, i + 1):
dp[i] += dp[j - 1] * dp[i - j]
return dp[n]
95. 不同的二叉搜索树 II(Medium)
Solu:DFS暴力枚举
dfs(start, end)= 用nodestart ~ end(闭区间)可以生成哪些BST
Code:
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
def dfs(start, end):
if start > end:
return [None]
if start == end:
return [TreeNode(start)]
res = []
for i in range(start, end + 1):
left = dfs(start, i - 1)
right = dfs(i + 1, end)
for l in left:
for r in right:
root = TreeNode(i, l, r)
res.append(root)
return res
return dfs(1, n) if n else []
LCA
本质上是:
- 拿到所有childNode的处理结果
- 在当前节点汇总来自所有childNode的数据,整理出一个新的答案(data自下而上)
❤️ 模版
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or p == root or q == root:
return root
left, right = self.lowestCommonAncestor(root.left, p, q), self.lowestCommonAncestor(root.right, p, q)
if left and right: # p, q 分居在左右子树中
return root
return left if left else right # p, q 都在左子树或右子树中
235. 二叉搜索树的最近公共祖先(Easy)
Solu:
- 如果
p或q中一个落在root上 或者p,q分居root两侧,那么root即为LCA - 否则,在
p,q所在的那一侧subtree上递归搜索
Code:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if p.val > q.val: # 易于比较,保证p是val更小的那个node
p, q = q, p
if root.val == p.val or root.val == q.val or p.val < root.val < q.val:
return root
elif root.val < p.val:
return self.lowestCommonAncestor(root.right, p, q)
else:
return self.lowestCommonAncestor(root.left, p, q)
236. 二叉树的最近公共祖先(Medium)
Solu 1:
照抄模版
- if
lowestCommonAncestor(root, p, q) is not None,then 表明在以root为根节点的tree上找到了p或者q- if
root == p or root == q, 表明LCA必定是root - if
left and right, then 表明p,q分居root两侧 - if
left或者 ifright, 表明p,q同时都在root的左/右子树
- if
Code 1:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or p == root or q == root:
return root
left, right = self.lowestCommonAncestor(root.left, p, q), self.lowestCommonAncestor(root.right, p, q)
if left and right: # p, q 分居在左右子树中
return root
return left if left else right # p, q 都在左子树或右子树中
Solu 2:Map
memo记录每个node的最近祖先(即:parent)- 当
p,q各自通往root的path第一次产生交集,则说明找到了它们的LCA
Code 2:
class Solution:
def __init__(self):
self.memo = {}
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def dfs(node, parent) -> None:
if not node:
return
self.memo[node] = parent
dfs(node.left, node)
dfs(node.right, node)
dfs(root, None)
seen = set()
while p or q:
if p and p in seen: # 产生交集
return p
if p:
seen.add(p)
p = self.memo[p]
if q and q in seen: # 产生交集
return q
if q:
seen.add(q)
q = self.memo[q]
return None
1644. 二叉树的最近公共祖先 II(Medium)
Solu 1:
- 对模版做修改:改成postOrder,强制遍历所有nodes
Code 1:
class Solution:
def __init__(self):
self.seen = 0
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def dfs(node):
if not node:
return node
left = dfs(node.left)
right = dfs(node.right)
if node.val == p.val or node.val == q.val:
self.seen += 1
return node
if left and right:
return node
return left if left else right
ans = dfs(root)
return ans if self.seen == 2 else None
Solu 2:Map
memo记录每个node的最近祖先(也就是parent)- 一旦
p,q各自通往root的path产生了交集,则说明找到了LCA - 根据题意,要先checkp,q是否都在tree中
Code 2:
class Solution:
def __init__(self):
self.memo = {}
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def dfs(node, parent) -> None:
if not node:
return
self.memo[node] = parent
dfs(node.left, node)
dfs(node.right, node)
dfs(root, None)
seen = set()
if p in self.memo and q in self.memo:
while p or q:
if p and p in seen: # 产生交集,找到LCA
return p
if p:
seen.add(p)
p = self.memo[p]
if q and q in seen: # 产生交集,找到LCA
return q
if q:
seen.add(q)
q = self.memo[q]
return None
1650. 二叉树的最近公共祖先 III(Medium)
Solu:“一路向北”
p和q都同时往root进发,一旦两条paths产生交集,则说明找到了LCA
Code:
class Solution:
def __init__(self):
self.seen = set()
def lowestCommonAncestor(self, p: 'Node', q: 'Node') -> 'Node':
while p or q:
if p and p in self.seen:
return p
if p:
self.seen.add(p)
p = p.parent
if q and q in self.seen:
return q
if q:
self.seen.add(q)
q = q.parent
return None
1676. 二叉树的最近公共祖先 IV(Medium)
Solu:
照抄模版,略
Code:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', nodes: 'List[TreeNode]') -> 'TreeNode':
if not root or root in nodes:
return root
left, right = self.lowestCommonAncestor(root.left, nodes), self.lowestCommonAncestor(root.right, nodes)
if left and right:
return root
return left if left else right
1123. 最深叶节点的最近公共祖先(Medium)
Solu 1:two-pass
- pass 1: 先通过BFS找到所有deepest leaf nodes
- pass 2: 照抄模版,找所有deepest leaf nodes的LCA(参见 LC 1676)
Code 1:
class Solution:
def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
def findDeepestLeaves():
deepest = []
q = [root]
while q:
deepest = []
for _ in range(len(q)):
node = q.pop(0)
deepest.append(node)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
return deepest
def LCAMultiNodes(node, targets):
if not node or node in targets:
return node
left = LCAMultiNodes(node.left, targets)
right = LCAMultiNodes(node.right, targets)
if left and right:
return node
return left if left else right
deepest = findDeepestLeaves()
return LCAMultiNodes(root, deepest)
Solu 2:one-pass ❤️
dfs(node)同时返回:- 以
node为根节点的tree的最大深度 - 以
node为根节点的tree的deepest leaf nodes' LCA
- 以
- if 左右子树的最大深度一样,则说明deepest Leaf nodes分居
root两侧,LCA = root - Otherwise, 所有的deepest leaf nodes都在具有更大最大深度的那一侧,且 那一侧subtree的LCA = 整个tree的LCA
Code 2:
class Solution:
def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
def dfs(node):
'''
同时找到以node为根的树的(最大深度 and 最深叶子结点的LCA)
:param node:
:return:
'''
if not node:
return 0, None
left_height, left_node = dfs(node.left)
right_height, right_node = dfs(node.right)
if left_height > right_height:
return left_height + 1, left_node
elif left_height < right_height:
return right_height + 1, right_node
else:
return left_height + 1, node
return dfs(root)[1]
信息传递
拿到当前node的信息后,可以自上而下传递给left, right node, 也可以自下而上传递给parent node
- backtracking / DFS:自上而下
- 在走一条路不通后,我们undo我们的操作,去下一条路再次尝试
- “pure recursion”:自下而上
- base case:最小的不可再分的subproblem -> leaf node
- induction rule:如何利用子问题的结果 -> 当前node处理leftChild, rightChild返回来的结果
backtracking(自上而下)
- 从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束
- 不可以拐弯
模版 ❤️
# 一般路径
def dfs(node, path):
if not node:
return
path.append(node.val)
if not node.left and not node.right: # leaf node
res.ans.append(path[:])
dfs(node.left, path)
dfs(node.right, path)
path.pop() # backtracking
# 给定sum的路径
def dfs(node, path, sum):
if not node:
return
sum -= node.val
path.append(node.val)
if not node.left and not node.right and sum == 0: # leaf node and sum == 0, add valid path
res.ans.append(path[:])
dfs(node.left, path, sum)
dfs(node.right, path, sum)
path.pop() # backtracking
257. 二叉树的所有路径
Solu:DFS回溯
照抄模版,在leaf node处添加path
Code:
class Solution:
def __init__(self):
self.ans = []
def binaryTreePaths(self, root: TreeNode) -> List[str]:
def dfs(node, path) -> None:
if not node:
return
path.append(node.val)
if not node.left and not node.right:
self.ans.append('->'.join(map(str, path)))
dfs(node.left, path)
dfs(node.right, path)
path.pop() # backtracking
dfs(root, [])
return self.ans
1448. 统计二叉树中好节点的数目(Medium)
Solu:DFS回溯
- 自上而下传递是一个目前path的最大值,只要
curNode >= 传下来的最大值,那么我们就得到了我们的一个答案(good nodes) - 一般backtracking写成
void函数
Code:
class Solution:
def __init__(self):
self.ans = 0
def goodNodes(self, root: TreeNode) -> int:
def dfs(node, max_val):
if not node:
return
if node.val >= max_val:
self.ans += 1
dfs(node.left, max(max_val, node.val))
dfs(node.right, max(max_val, node.val))
dfs(root, root.val)
return self.ans
"pure recursion"(自下而上)
从任意节点到任意节点的路径,不需要自顶向下(可以拐弯)
模版 ❤️
class Solution:
def __init__(self):
self.res = 0
def maxPath(self, root): # 以root为路径起始点的最长路径
if not root:
return 0
# postOrder强行遍历所有nodes
left = self.maxPath(root.left)
right = self.maxPath(root.right)
self.res = max(self.res, left + right + root.val) # 穿过root的最长路径,更新res
return max(left, right) + root.val # 返回以root为路径起始点的最长路径
基础recursion(返回单一数据)
124. 二叉树中的最大路径和(Hard)
Solu:
maxPath(node):- return 以node为起始点的具有最大sum的path
- 同时更新穿过node的具有最大sum的path(“打擂台”)
Code:
class Solution:
def __init__(self):
self.ans = -sys.maxsize
def maxPathSum(self, root: Optional[TreeNode]) -> int:
def maxPath(node) -> int:
if not node:
return 0
left = max(maxPath(node.left), 0)
right = max(maxPath(node.right), 0)
self.ans = max(self.ans, node.val + left + right)
return node.val + max(left, right)
maxPath(root)
return self.ans
进阶recursion(返回多个同一类型的数据)
1120. 子树的最大平均值(Medium)
Solu:
- 因为需要计算所有subtree的avg,所以对于一个tree:
- 采用postOrder强行遍历所有的subtree,“打擂台”不断更新
ans - 同时对于一个node,需要得知其左右子树各自的
#nodes和sumOfTree,才能计算自己这整棵tree的avg
- 采用postOrder强行遍历所有的subtree,“打擂台”不断更新
Code:
class Solution:
def __init__(self):
self.ans = float('-inf')
def maximumAverageSubtree(self, root: TreeNode) -> float:
def maxAvg(node): # 返回以node为根节点tree的[sum, #nodes],同时“打擂台”更新max average
if not node:
return 0, 0
# postOrder
lsum, lnodes = maxAvg(node.left)
rsum, rnodes = maxAvg(node.right)
self.ans = max(self.ans, float(lsum + rsum + node.val) / (lnodes + rnodes + 1))
return lsum + rsum + node.val, lnodes + rnodes + 1
maxAvg(root)
return self.ans
1372. 二叉树中的最长交错路径(Medium)
Solu:
dfs(node, isLeft):- return 从
node出发,以isLeft为起始方向的zigzagPath中有多少个nodes - 同时“打擂台”更新从一个节点出发的最长zigzagPath中有多少个nodes
- return 从
Code:
class Solution:
def __init__(self):
self.ans = 0
def longestZigZag(self, root: TreeNode) -> int:
def dfs(node, isLeft): # 返回在以node为根节点的tree中,按照指定的方向从root出发得到的zigzagPath有多少个nodes
if not node:
return 0
l = dfs(node.left, False)
r = dfs(node.right, True)
self.ans = max(self.ans, max(l, r) + 1)
return 1 + (l if isLeft else r)
dfs(root, False)
return self.ans - 1
549. 二叉树中最长的连续序列(Medium)
Solu:
dfs(node):- return:
incr: 当前以node为根节点的tree中,从node出发的最长连续递增序列的长度decr: 当前以node为根节点的tree中,从node出发的最长连续递减序列的长度
- 同时“打擂台”,用 穿过node的最长连续序列的长度 更新
ans
- return:
Code:
class Solution:
def __init__(self):
self.ans = 0
def longestConsecutive(self, root: TreeNode) -> int:
def dfs(node): # return [incr: node里面的以node为起点的最长连续递增序列长度, decr:以node为起点的最长连续递减序列长度]
if not node:
return 0, 0
lincr, ldecr = dfs(node.left)
rincr, rdecr = dfs(node.right)
if node.left and node.left.val == node.val + 1:
ldecr = 0
elif node.left and node.left.val == node.val - 1:
lincr = 0
else: # 不连续
lincr = 0
ldecr = 0
if node.right and node.right.val == node.val + 1:
rdecr = 0
elif node.right and node.right.val == node.val - 1:
rincr = 0
else: # 不连续
rincr = 0
rdecr = 0
self.ans = max(self.ans, ldecr + rincr + 1, lincr + rdecr + 1)
return max(lincr, rincr) + 1, max(ldecr, rdecr) + 1
dfs(root)
return self.ans
其他
❤️ 979. 在二叉树中分配硬币(Medium)
Solu:
- 假设
node的左儿子已经尽力处理了问题,目前已经走了l_step步,但还是有需求l_demand;相似的,右儿子已经走了r_step步,还有需求r_demand - 父节点
node为了拯救儿子们,赊账也要搞定。因此自己需要再移动|l_demand| + |r_demand|步;此时儿子们虽然平衡了,但父节点字自己现在的需求变成了node.val + l_demand + r_demand-1 - 问题在
root处被解决,因为本身“家族”内的钱总量是能平衡的,只是分配不均。ans = 一共操作了多少步
Code:
class Solution:
def distributeCoins(self, root: Optional[TreeNode]) -> int:
def dfs(node):
if not node:
return 0, 0
l_step, l_demand = dfs(node.left)
r_step, r_demand = dfs(node.right)
total_step = l_step + r_step + abs(l_demand) + abs(r_demand)
total_demand = (l_demand + r_demand + node.val) - 1
return total_step, total_demand
return dfs(root)[0]
Reference: