3个入门级简单题,汇总一下:
101. 对称二叉树
class Solution:
def isSymmetric(self, root):
# 56ms 击败10%
# return self.check(root, root)
if root is None:
return True
# 52ms 击败20%
return self.check(root.left, root.right)
def check(self, p, q):
if p is None and q is None:
return True
if p is None or q is None:
return False
return p.val == q.val and self.check(p.left, q.right) and self.check(p.right, q.left)
226. 翻转二叉树
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if root is None:
return None
root.right, root.left = self.invertTree(root.left), self.invertTree(root.right)
return root
剑指 Offer 27. 二叉树的镜像
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
def recur(node):
if node is None:
return None
node.left, node.right = recur(node.right), recur(node.left)
return node
return recur(root)
这种类型的题有两个关键步骤:
543. 二叉树的直径
有空重新刷一下,注意边界条件
class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
ans = 1
def rec(node:TreeNode):
nonlocal ans
if node is None:
return 0
rl, rr = rec(node.left), rec(node.right)
# 更新:经过当前结点的最大长度
ans = max(ans, 1 + rl + rr)
# 返回:从当前结点出发的最大长度
return max(rl, rr) + 1
rec(root)
return ans - 1
124. 二叉树中的最大路径和
class Solution:
def maxPathSum(self, root: TreeNode) -> int:
ans = -inf
def recursion(node)->int:
nonlocal ans
if node is None:
return 0
# 如果为负数,做一个截断
left = max(0, recursion(node.left))
right = max(0, recursion(node.right))
# 更新:经过当前结点的最大路径和
ans = max(ans, left + right + node.val)
# 返回:从当前结点出发的最大路径
return node.val + max(left, right)
recursion(root)
return ans
最大深度
104. 二叉树的最大深度
class Solution:
def maxDepth(self, root: TreeNode) -> int:
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 if root else 0
最小深度
111. 二叉树的最小深度
对于【二叉树的最小深度】,虽然BFS和DFS的时间复杂度相同,但一般来说BFS会更快,因为一般情况下只需要遍历部分结点即可返回
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
if root.left is None and root.right is None:
return 1
ans = inf
if root.left:
ans = min(ans, self.minDepth(root.left))
if root.right:
ans = min(ans, self.minDepth(root.right))
return ans + 1
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root: return 0
q = [(root, 1)]
while q:
top, d = q.pop(0)
if top.left is None and top.right is None:
return d
if top.left:
q.append((top.left, d + 1))
if top.right:
q.append((top.right, d + 1))
116. 填充每个节点的下一个右侧节点指针
题目要求常数空间。
class Solution:
def connect(self, root: 'Node') -> 'Node':
if root is None:
return None
def recursion(node1, node2):
if node1 is None or node2 is None:
return
node1.next = node2
recursion(node1.left, node1.right)
recursion(node2.left, node2.right)
recursion(node1.right, node2.left)
recursion(root.left, root.right)
return root
跑起来快多了。。
class Solution:
def connect(self, root: 'Node') -> 'Node':
if root is None:
return None
queue = collections.deque()
queue.append(root)
while queue:
sz = len(queue)
pre = None
for _ in range(sz):
top = queue.popleft()
if pre:
pre.next = top
pre = top
if top.left:
queue.append(top.left)
if top.right:
queue.append(top.right)
return root
117. 填充每个节点的下一个右侧节点指针 II
这题与上题不同,只能用BFS层序遍历求解了
662. 二叉树最大宽度
class Solution:
def widthOfBinaryTree(self, root: TreeNode) -> int:
q = [(root, 0)]
ans = 0
max_width = 0
while q:
sz = len(q)
lb, rb = inf, -inf
for i in range(sz):
top, idx = q.pop(0)
lb = min(lb, idx)
rb = max(rb, idx)
max_width = max(max_width, (rb - lb + 1))
if top.left:
q.append((top.left, idx * 2))
if top.right:
q.append((top.right, idx * 2 + 1))
return max_width
这类题目有一个共同的特点:
二者叠加后,时间复杂度就变成了 log 2 N \log^2N log2N
222. 完全二叉树的节点个数
算法笔记:如何计算完全二叉树的节点数
利用完全二叉树的性质:
height_left == height_right
时间复杂度 O ( l o g 2 N ) O(log^2N) O(log2N)
class Solution:
def countNodes(self, root: TreeNode) -> int:
r = l = root
hr = hl = 0
while r is not None:
hr += 1
r = r.right
while l is not None:
hl += 1
l = l.left
# 空指针也会在这里返回
if hl == hr:
return 2 ** (hl) - 1
return 1 + self.countNodes(root.left) + self.countNodes(root.right)
字节跳动面试题.完全二叉树的最后一个节点
实习|算法岗血泪面经:商汤,旷世,阿里,字节跳动
这题的lh
,rh
与上题的区别:
lh
:从左节点出发,一直往左走
rh
:从右结点出发,一直往右左走
用这张图简单说明一下思路:
左子树高度>右子树高度
,目标结点必然在左子树左子树高度<=右子树高度
,目标结点必然在右子树class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def getLastNode(root: TreeNode) -> TreeNode:
if root is None or root.left is None:
return root
lp, rp = root.left, root.right
lh = rh = 0
while lp:
lp = lp.left
lh += 1
while rp:
rp = rp.left
rh += 1
if lh > rh:
return getLastNode(root.left)
elif lh <= rh:
return getLastNode(root.right)
if __name__ == '__main__':
case1 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, TreeNode(6)))
assert getLastNode(case1).val == 6
case2 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, TreeNode(6), TreeNode(7)))
assert getLastNode(case2).val == 7
case3 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3))
assert getLastNode(case3).val == 5
case4 = TreeNode(1,
TreeNode(2, TreeNode(4, TreeNode(8), TreeNode(9)), TreeNode(5, TreeNode(10))),
TreeNode(3, TreeNode(6), TreeNode(7)))
assert getLastNode(case4).val == 1
958. 二叉树的完全性检验
class Solution:
def isCompleteTree(self, root: TreeNode) -> bool:
i = 0
nodes = [(root, 1)]
while i < len(nodes):
node, v = nodes[i]
i += 1
if node:
nodes.append([node.left, v * 2])
nodes.append([node.right, v * 2 + 1])
return len(nodes) == nodes[-1][1]
919. 完全二叉树插入器
class CBTInserter:
def __init__(self, root: TreeNode):
self.root = root
queue = [root]
self.heap = []
while queue:
top = queue.pop(0)
self.heap.append(top)
if top.left:
queue.append(top.left)
if top.right:
queue.append(top.right)
def insert(self, v: int) -> int:
node = TreeNode(v)
self.heap.append(node)
n = len(self.heap)
pid = n // 2 - 1
parent = self.heap[pid]
if n % 2:
parent.right = node
else:
parent.left = node
return parent.val
def get_root(self) -> TreeNode:
return self.root
手把手带你刷二叉搜索树(第二期)
手把手带你刷通二叉搜索树(第三期)
700. 二叉搜索树中的搜索
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
if root is None or root.val == val:
return root
if root.val < val:
return self.searchBST(root.right, val)
else:
return self.searchBST(root.left, val)
701. 二叉搜索树中的插入操作
class Solution:
def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode:
if root is None:
return TreeNode(val)
if root.val < val:
root.right = self.insertIntoBST(root.right, val)
else:
root.left = self.insertIntoBST(root.left, val)
return root
450. 删除二叉搜索树中的节点
左子树中最大的结点
或 [ 右子树最小结点
] 接替自己
class Solution:
def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
if root is None:
return None
if root.val == key:
# == 情况1 == & == 情况2 ==
if root.left is None:
return root.right
if root.right is None:
return root.left
# == 情况3 ==
# 找到右子树的最小结点,即右子树不断往左遍历的最后一个结点
min_node = self.find_min(root.right)
# 【这个结点】和【待删除结点】做swap,但具体的操作形式为赋值:
# 【待删除结点】.val = 【右子树最小结点】.val
root.val = min_node.val
# 转化为子问题:删除【右子树最小结点】
root.right = self.deleteNode(root.right, min_node.val)
elif root.val > key: # 左右顺序写错
root.left = self.deleteNode(root.left, key)
elif root.val < key:
root.right = self.deleteNode(root.right, key)
return root
def find_min(self, root: TreeNode) -> TreeNode:
while root.left:
root = root.left
return root
面试题 04.05. 合法二叉搜索树
98. 验证二叉搜索树
我们通过使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,这也是二叉树算法的一个小技巧吧。
class Solution:
# def isValidBST(self, root: TreeNode) -> bool:
def isValidBST(self, root: TreeNode, min_: TreeNode = None, max_: TreeNode = None) -> bool:
if root is None:
return True
if min_ is not None and root.val <= min_.val:
return False
if max_ is not None and root.val >= max_.val:
return False
return self.isValidBST(root.left, min_, root) and \
self.isValidBST(root.right, root, max_)
这个专题的题目其实在本文的其他部分都有收录,但笔者发现了这些题的共通之处,故做一整理。
本专题为几道涉及BST的的题目,且解题函数中都涉及到了lower
、upper
参数,这两个参数会在递归过程中判断当前结点是否是调用方的一个合法子结点(left
or right
),如果不是,会返回None
1008. 前序遍历构造二叉搜索树
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
index = 0
N = len(preorder)
def dfs(lower, upper):
nonlocal index
if not 0 <= index < N:
return None
if preorder[index] < lower or preorder[index] > upper:
return None
cur = preorder[index]
node = TreeNode(cur)
index += 1
node.left = dfs(lower, cur)
node.right = dfs(cur, upper)
return node
return dfs(-inf, inf)
449. 序列化和反序列化二叉搜索树
lower
和upper
界定当前index的元素是否合法class Codec:
def serialize(self, root: TreeNode) -> str:
def helper(root):
if root is None: return []
left = helper(root.left)
right = helper(root.right)
return [str(root.val)] + left + right
return ",".join(helper(root))
def deserialize(self, data: str) -> TreeNode:
if not data: return None
seq = [int(x) for x in data.split(",")]
def helper(lower=-inf, upper=inf):
nonlocal seq
if not seq or (not lower<seq[0]<upper):
return None
root = TreeNode(seq.pop(0))
root.left = helper(lower, root.val)
root.right = helper(root.val, upper)
return root
return helper()
面试题 04.05. 合法二叉搜索树
98. 验证二叉搜索树
class Solution:
# def isValidBST(self, root: TreeNode) -> bool:
def isValidBST(self, root: TreeNode, lower: TreeNode = None, upper: TreeNode = None) -> bool:
if root is None:
return True
if lower is not None and root.val <= lower.val:
return False
if upper is not None and root.val >= upper.val:
return False
return self.isValidBST(root.left, lower, root) and \
self.isValidBST(root.right, root, upper)
669. 修剪二叉搜索树
class Solution:
def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode:
if root is None:
return None
if root.val < low:
return self.trimBST(root.right, low, high)
elif root.val > high:
return self.trimBST(root.left, low, high)
else:
root.left = self.trimBST(root.left, low, high)
root.right = self.trimBST(root.right, low, high)
return root
手把手带你刷二叉搜索树(第一期)
230. 二叉搜索树中第K小的元素
时间复杂度 O ( K ) O(K) O(K)
class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:
rank = 0
ans = -1
def rec(node: TreeNode):
nonlocal rank, ans
if node is None:
return
rec(node.left)
rank += 1
if rank == k:
ans = node.val
return
rec(node.right)
rec(root)
return ans
剑指 Offer 54. 二叉搜索树的第k大节点
面试题54. 二叉搜索树的第 k 大节点(中序遍历 + 提前返回,清晰图解)
题解的Python代码用类变量代替了我答案的局部变量,值得学习
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
cnt = 0
ans = None
def travel(node):
nonlocal cnt, ans
if node is None or cnt > k:
return
travel(node.right)
cnt += 1
if cnt == k:
ans = node
travel(node.left)
travel(root)
return ans.val
538. 把二叉搜索树转换为累加树
同样利用了BST中序遍历递增的性质
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
sum_ = 0
def rec(node: TreeNode):
nonlocal sum_
if node is None:
return None
rec(node.right)
sum_ += node.val
node.val = sum_
rec(node.left)
rec(root)
return root
501. 二叉搜索树中的众数
# 一次遍历得到结果
class Solution:
def findMode(self, root: TreeNode) -> List[int]:
ans = []
cnt = 0
pre = nan
max_cnt = 0
def rec(root):
nonlocal ans, cnt, pre, max_cnt
if root is None:
return
rec(root.left)
# travel
cur = root.val
if cur == pre:
cnt += 1
else:
cnt = 1
pre = cur
if cnt > max_cnt:
max_cnt = cnt
ans = [cur]
elif cnt == max_cnt:
ans.append(cur)
# end travel
rec(root.right)
rec(root)
return ans
99. 恢复二叉搜索树
方法一、显式中序遍历
之前刷东哥算法小抄的时候刷到了这个题,在没看题解的情况下写了个时间复杂度 O ( N log N ) O(N\log N) O(NlogN)的代码,解法特别高效,中序遍历后,排序,再中序遍历一次。
如果用【显式中序遍历】方法的话,需要用for循环找到是哪两个结点乱序了,然后交换两个结点的值就好了。
class Solution:
def recoverTree(self, root: TreeNode) -> None:
seq = []
def inorder(root):
if not root:
return
inorder(root.left)
seq.append(root)
inorder(root.right)
inorder(root)
x = y = None
n = len(seq)
# 那[3,2,1]的例子推这个步骤
for i in range(n - 1):
if seq[i].val > seq[i + 1].val:
y = seq[i + 1]
if x is None:
x = seq[i]
else:
break
x.val, y.val = y.val, x.val
1373. 二叉搜索子树的最大键值和
美团面试官:你对后序遍历一无所知
class Solution:
def maxSumBST(self, root: TreeNode) -> int:
self.ans = -inf
def travel(root):
if root is None:
return inf, -inf, 0, True
left_min, left_max, left_sum, left_valid = travel(root.left)
right_min, right_max, right_sum, right_valid = travel(root.right)
if left_valid and right_valid and left_max < root.val < right_min:
cur_sum = left_sum + right_sum + root.val
self.ans = max(self.ans, cur_sum)
return min(left_min,root.val), max(right_max,root.val), cur_sum, True
else:
return 0, 0, 0, False
travel(root)
return max(self.ans,0)
剑指 Offer 55 - II. 平衡二叉树
面试题55 - II. 平衡二叉树(从底至顶、从顶至底,清晰图解)
递归判断:左右子树高度差 < 2
类似于二叉树的后序遍历
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def recur(root):
if root is None:
return 0
left = recur(root.left)
right = recur(root.right)
if left==-1 or right==-1:
return -1
if abs(right-left)>=2:
return -1
return max(left, right) + 1
return recur(root)!=-1
面试题 04.02. 最小高度树
return TreeNode(nums[len(nums) // 2], self.sortedArrayToBST(nums[:len(nums) // 2]),
self.sortedArrayToBST(nums[len(nums) // 2 + 1:])) if nums else None
首先阐述一个概念:子结构匹配与子树匹配
子树匹配:
子结构匹配就是A的子结构与B部分匹配,A的子结构允许有与B不匹配的其他结构
如何判断是【子树匹配】还是【子结构】呢?我觉得最重要的还是读题。
并且这两个匹配算法在代码上相差比较小,如果用某种方法A不了我们可以想想是不是思考错题意了
剑指 Offer 26. 树的子结构
建议看这个题解:
面试题26. 树的子结构(先序遍历 + 包含判断,清晰图解)
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
recur
判定函数的记忆方法:
递归里面有递归,很难想到
def recur(A, B):
if B is None:
return True
if A is None:
return False
if not A.val==B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right) # and
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
# bool
return bool(A and B) and ( recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B) )
572. 另一个树的子树
这题如果是子结构匹配的话,示例2是可以通过的(但子树匹配的返回值为false)。我们做题的第一件事就是理解题意。
这题其实可以复用上题的代码,就是需要修改一下recur
函数的判断条件
【子结构匹配】的recur函数:
def recur(A, B):
if B is None: # 只要到了B的边界(None值)就视为完成了匹配
return True
if A is None:
return False
if not A.val==B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right) # and
【子树匹配】的recur函数:
def recur(A, B):
if A is None and B is None: # B到了边界时,A必须也到边界
return True
if bool(A) ^ bool(B): # 如果AB其中之一为None,另外一个不为None,就表明匹配失败
return False
if not A.val==B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right)
相同点:
AB都不为None,值(val)不匹配,返回False
不同点:(加粗)
A | B | 子树匹配 | 子结构匹配 |
---|---|---|---|
None | not None | False | False |
None | None | True | True |
not None | None | False | True |
子树匹配完整代码:
def recur(A, B):
if A is None and B is None:
return True
if bool(A) ^ bool(B):
return False
if not A.val==B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right)
class Solution:
def isSubtree(self, A: TreeNode, B: TreeNode) -> bool:
return bool(A and B) and ( recur(A, B) or self.isSubtree(A.left, B) or self.isSubtree(A.right, B) )
652. 寻找重复的子树
东哥笔记:
手把手带你刷二叉树(第三期)
本质上是数的后序遍历,使用了序列化的技巧
class Solution:
NULL = "#"
def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]:
memo = collections.defaultdict(int)
res = []
def traverse(node: TreeNode):
if node is None:
return self.NULL
seq = ",".join([traverse(node.left), traverse(node.right), str(node.val)])
if memo[seq] == 1: # 巧妙地去重
res.append(node)
memo[seq] += 1
return seq
traverse(root)
return res
297. 二叉树的序列化与反序列化
二叉树的题,就那几个框架,枯燥至极
class Codec:
NULL = '#'
def serialize(self, root: TreeNode) -> str:
res = []
def traverse(root: TreeNode):
if root is None:
res.append(self.NULL)
else:
res.append(str(root.val))
traverse(root.left)
traverse(root.right)
traverse(root)
return ','.join(res)
def deserialize(self, data: str) -> TreeNode:
lst = data.split(',')
def traverse():
val = lst.pop(0)
if val == self.NULL:
return None
else:
node = TreeNode(int(val))
node.left = traverse()
node.right = traverse()
return node
return traverse()
val = lst.pop()
)class Codec:
NULL = '#'
def serialize(self, root: TreeNode) -> str:
res = []
def traverse(root: TreeNode):
if root is None:
res.append(self.NULL)
else:
traverse(root.left)
traverse(root.right)
res.append(str(root.val))
traverse(root)
return ','.join(res)
def deserialize(self, data: str) -> TreeNode:
lst = data.split(',')
def traverse():
# 与中序不同,取出最后的元素
val = lst.pop()
if val == self.NULL:
return None
else:
node = TreeNode(int(val))
# 先右后左
node.right = traverse()
node.left = traverse()
return node
return traverse()
class Codec:
NULL = '#'
def serialize(self, root: TreeNode) -> str:
q = collections.deque()
q.append(root)
seq = []
while q:
top = q.popleft()
seq.append(str(top.val) if top else self.NULL)
if top is not None:
q.append(top.left)
q.append(top.right)
return ",".join(seq)
def deserialize(self, data: str) -> TreeNode:
def decode(elem: str) -> Optional[TreeNode]:
if elem == self.NULL:
return None
return TreeNode(int(elem))
seq = data.split(",")
q = collections.deque()
root = decode(seq.pop(0))
q.append(root)
while q and seq:
top = q.popleft()
if top is not None:
left = decode(seq.pop(0))
right = decode(seq.pop(0))
top.left = left
top.right = right
q.append(left)
q.append(right)
return root
class Codec:
NULL = 'null'
def serialize(self, root: TreeNode) -> str:
q = collections.deque()
q.append(root)
seq = []
while q:
top = q.popleft()
seq.append(str(top.val) if top else self.NULL)
if top is not None:
q.append(top.left)
q.append(top.right)
# 删除后导null
index = len(seq) - 1
while index >= 0 and seq[index] == self.NULL: index -= 1
seq = seq[:index + 1]
print(seq)
return "[" + ",".join(seq) + "]"
def deserialize(self, data: str) -> TreeNode:
def decode(seq) -> Optional[TreeNode]:
if not seq:
return None
elem = seq.pop(0)
if elem == self.NULL:
return None
return TreeNode(int(elem))
if data == "[]": return None
seq = data[1:-1].split(",")
q = collections.deque()
root = decode(seq)
q.append(root)
while q and seq:
top = q.popleft()
if top is not None:
left = decode(seq)
right = decode(seq)
top.left = left
top.right = right
q.append(left)
q.append(right)
return root
449. 序列化和反序列化二叉搜索树
lower
和upper
界定当前index的元素是否合法class Codec:
def serialize(self, root: TreeNode) -> str:
def helper(root):
if root is None: return []
left = helper(root.left)
right = helper(root.right)
return [str(root.val)] + left + right
return ",".join(helper(root))
def deserialize(self, data: str) -> TreeNode:
if not data: return None
seq = [int(x) for x in data.split(",")]
def helper(lower=-inf, upper=inf):
nonlocal seq
if not seq or (not lower<seq[0]<upper):
return None
root = TreeNode(seq.pop(0))
root.left = helper(lower, root.val)
root.right = helper(root.val, upper)
return root
return helper()
652. 寻找重复的子树
东哥笔记:
手把手带你刷二叉树(第三期)
本质上是数的后序遍历,使用了序列化的技巧
class Solution:
NULL = "#"
def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]:
memo = collections.defaultdict(int)
res = []
def traverse(node: TreeNode):
if node is None:
return self.NULL
seq = ",".join([traverse(node.left), traverse(node.right), str(node.val)])
if memo[seq] == 1: # 巧妙地去重
res.append(node)
memo[seq] += 1
return seq
traverse(root)
return res
https://www.cnblogs.com/TQCAI/p/8546737.html
这个类型的题,一般以下几种出题方法:
105. 从前序与中序遍历序列构造二叉树
preorder
的最开始的元素就是根节点元素:preorder[pre_s]
找到inorder中的下标:i = inorder.index(preorder[pre_s])
左子树元素数量:num_left = i - in_s
递归:
pre_s + 1, pre_s + num_left
in_s, i - 1
pre_s + num_left + 1, pre_e
i + 1, in_e
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def build(pre_s, pre_e, in_s, in_e):
if pre_s > pre_e or in_s > in_e:
return None
if pre_s == pre_e:
return TreeNode(preorder[pre_s])
i = inorder.index(preorder[pre_s])
num_left = i - in_s
node = TreeNode(preorder[pre_s])
node.left = build(pre_s + 1, pre_s + num_left, in_s, i - 1)
node.right = build(pre_s + num_left + 1, pre_e, i + 1, in_e)
return node
N = len(inorder)
return build(0, N - 1, 0, N - 1)
106. 从中序与后序遍历序列构造二叉树
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
def build(post_s, post_e, in_s, in_e):
if post_s > post_e or in_s > in_e:
return None
if post_s == post_e:
return TreeNode(postorder[post_e])
i = inorder.index(postorder[post_e])
num_left = i - in_s
node = TreeNode(postorder[post_e])
node.left = build(post_s, post_s + num_left - 1, in_s, i - 1)
node.right = build(post_s + num_left, post_e - 1, i + 1, in_e)
return node
N = len(inorder)
return build(0, N - 1, 0, N - 1)
多年前还在学土木时写的:分析报告
1
/ \
2 4
/ \ \
3 5 6
pre: 1 [2 3 5] [4
6]
post: [3 5 2] [6 4
] 1
如果满足i == postE - 1
,就说明当前结点既可以是左子树,也可以是右子树(paradox
结点)
int pre[LEN] = {8, 5, 2, 6, 10, 9, 11};
int in[LEN];
int post[LEN] = {2, 6, 5, 9, 11, 10, 8};
int t = 0;
int flag = 1;
void setIn(int preS, int preE, int postS, int postE) {
if (preS == preE) {
in[t++] = pre[preS];
return;
}
//finding the elem which is the root of left sub_tree
int i = postS;
while (i <= postE && post[i] != pre[preS + 1]) i++;
//calculate the numbers of left sub_tree
int leftNum = i - postS + 1;
//is paradox
if (i == postE - 1) {
flag = 0;
setIn(preS + 1, preS + leftNum, postS, i);//default consider this is a right leaf
in[t++] = pre[preS];
return;
}
//build the in_order traversal sequence
setIn(preS + 1, preS + leftNum, postS, i);
in[t++] = pre[preS];
setIn(preS + leftNum + 1, preE, i + 1, postE - 1);
}
setIn(0, n - 1, 0, n - 1);
实战一下:889. 根据前序和后序遍历构造二叉树
class Solution:
def constructFromPrePost(self, pre: List[int], post: List[int]) -> TreeNode:
N = len(pre)
if N == 0:
return None
if N == 1:
return TreeNode(pre[0])
# 左子树根节点
left_root = pre[1]
# 算出左子树结点数
n_left = post.index(left_root) + 1
return TreeNode(
pre[0],
self.constructFromPrePost(pre[1:1 + n_left], post[:n_left]),
self.constructFromPrePost(pre[1 + n_left:], post[n_left:-1]),
)
int cnt;
void calc(int preS, int preE, int postS, int postE) {
if (preS >= preE) return;
int i = postS;
while (i <= postE - 1 && post[i] != pre[preS + 1]) i++;
int ln = i - postS + 1; //left_num
if (i == postE - 1) cnt++;
calc(preS + 1, preS + ln, postS, postS + ln - 1);
calc(preS + ln + 1, preE, postS + ln, postE - 1);
}
在上文模板的基础上,在检测到有一组结点既可以当左子树,又可以当右子树时,cnt++
(记录这样的结点出现的个数)。最后输出cnt的二次幂(假如有一个这样的结点,那就有左右两种形态。如果有两个,在控制左右形态的同时,左右又各有左右两种形态,一次类推,比图cnt=3 ,ans就等于8 ……)
最后结果: a n s = c n t 2 ans=cnt^2 ans=cnt2
654. 最大二叉树
参考一个 O ( N ) O(N) O(N)的题解:最大二叉树
# inf | 3 2 1 6 0 5
# 找从当前往左第一个比当前元素大的
# 3->inf
# 2->3
# 1->2
# 6->inf
# 0->6
# 5->6
def build(nums: List[int], parent: TreeNode, index: int):
while 0 <= index < len(nums):
if nums[index] < parent.val:
now = TreeNode(nums[index])
# 如果当前结点有右结点,说明是回溯回来的
# tmp 比parent小,也比now小
tmp = parent.right
parent.right = now
now.left = tmp
# end
index = build(nums, now, index + 1)
else:
# 将index透传给上一个调用点
return index
return -1
class Solution(object):
def constructMaximumBinaryTree(self, nums):
parent = TreeNode(inf)
build(nums, parent, 0)
return parent.right
1008. 前序遍历构造二叉搜索树
首先可以想到两种时间复杂度为 O ( N log N ) O(N\log N) O(NlogN)的方法:
然后,我们看两种时间复杂度为 O ( N ) O(N) O(N)的方法:
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
index = 0
N = len(preorder)
def dfs(lb, ub):
nonlocal index
if not 0 <= index < N:
return None
if preorder[index] < lb or preorder[index] > ub:
return None
cur = preorder[index]
node = TreeNode(cur)
index += 1
node.left = dfs(lb, cur)
node.right = dfs(cur, ub)
return node
return dfs(-inf, inf)
自己在草稿纸上推一下,只要知道是【单调递减】栈就行了
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
root = TreeNode(preorder[0])
stack = [root]
for num in preorder[1:]:
node = TreeNode(num)
top = None
# 不满足【单调递减栈】的条件
while stack and not (num < stack[-1].val):
top = stack.pop()
if top is None:
stack[-1].left = node
else:
top.right = node
stack.append(node)
return root
对于这种类型的题,即给一个 前序/后序 的序列,判断是否为BST的 前序/后序 的序列。最开始可以想到一种比较朴素的想法:
不妨设已知preorder,则root.val = preorder[start]
,则可以找到r_start = lower_bound(preorder[start+1:], root.val)
,只要保证preorder[start+1:r_start]
preorder[r_start:]>root.val
,并且递归地判断这一结论,就可以得出最终的结论。
但这么做的时间复杂度为 O ( N 2 ) O(N^2) O(N2) — java 两种解法
但我们可以结合BST的结论,然后牢记这两个原则,就可以一举解决以下两道题了:
BST的postorder看的是倒序
如何判断:在遍历元素时,与出栈序列的栈顶进行判断,如果不满足就返回False
255. 验证前序遍历序列二叉搜索树
题解:Python3 图解,栈
入栈递减,出栈递增
class Solution:
def verifyPreorder(self, preorder: List[int]) -> bool:
# 出栈序列的栈顶
top = -inf
# 单调递减 栈
stack = []
for val in preorder:
# 不满足【出栈递增】的条件,即小于了出栈序列的栈顶
if val < top:
return False
# 不满足【单调递减】栈的条件
while stack and (not stack[-1] > val):
# 出栈序列的栈顶
top = stack.pop()
stack.append(val)
return True
剑指 Offer 33. 二叉搜索树的后序遍历序列
题解:面试题33. 二叉搜索树的后序遍历序列(递归分治 / 单调栈,清晰图解)
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
# 出栈序列的栈顶
top = inf
# 单调递增 栈
stack = []
for val in postorder[::-1]:
# 不满足【出栈递减】的条件,即小于了出栈序列的栈顶
if val > top:
return False
# 不满足【单调递增】栈的条件
while stack and (not stack[-1] < val):
# 出栈序列的栈顶
top = stack.pop()
stack.append(val)
return True
史上最全遍历二叉树详解
144. 二叉树的前序遍历
官方题解
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
ans = []
while root or stack:
while root:
# 访问
ans.append(root.val)
# 入栈,向左遍历
stack.append(root)
root = root.left
# 出栈,向右遍历
root = stack.pop()
root = root.right
return ans
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return []
stack = [root]
ans = []
while stack:
curr = stack.pop()
ans.append(curr.val)
left, right = curr.left, curr.right
if right:
stack.append(right)
if left:
stack.append(left)
return ans
94. 二叉树的中序遍历
官方题解
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
ans = []
while root or stack:
while root:
# 入栈,向左遍历
stack.append(root)
root = root.left
# 出栈
root = stack.pop()
# 访问
ans.append(root.val)
# 向右遍历
root = root.right
return ans
145. 二叉树的后序遍历
官方题解
最简单的想法就是利用后序遍历的性质:倒序是先右后左的先序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
ans = []
while root or stack:
while root:
ans.append(root.val)
stack.append(root)
root = root.right
root = stack.pop()
root = root.left
return ans[::-1]
236. 二叉树的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
# 直接对TreeNode对象进行判断就ok,甚至不需要对TreeNode.val进行判断
if root in [p, q, None]:
return root
l_ret = self.lowestCommonAncestor(root.left, p, q)
r_ret = self.lowestCommonAncestor(root.right, p, q)
if l_ret is None and r_ret is None:
return None
if l_ret is not None and r_ret is not None:
return root
return l_ret if l_ret else r_ret
1644. 二叉树的最近公共祖先 II
上一题的代码是不能用在这题的。为什么呢,观察代码会发现,如果p
在树中而q
不在树中,会把p
当做LCA返回。
c++几乎双百的递归解法
以下为解题思路,本质仍为后序遍历:
ans
为类对象dfs
,返回值为bool
:当前子树是否包含p或q。
True
,orp, q
p, q
,说明当前结点为ans
==p or ==q
,且某个子树含p, q
,说明当前结点为ans
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
self.ans: Optional[TreeNode] = None
self.dfs(root, p, q)
return self.ans
def dfs(self, root: TreeNode, p: TreeNode, q: TreeNode) -> bool:
if root is None:
return False
l_ret: bool = self.dfs(root.left, p, q)
r_ret: bool = self.dfs(root.right, p, q)
if l_ret and r_ret:
self.ans = root
if (l_ret or r_ret) and (root.val in [p.val, q.val]):
self.ans = root
if l_ret or r_ret:
return True
if root.val in [p.val, q.val]:
return True
return False
1650. 二叉树的最近公共祖先 III
class Solution:
def lowestCommonAncestor(self, p: 'Node', q: 'Node') -> 'Node':
a, b = p, q
while a != b:
a = a.parent if a else q
b = b.parent if b else p
return a
1676. 二叉树的最近公共祖先 IV
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', nodes: 'List[TreeNode]') -> 'TreeNode':
# 直接对TreeNode对象进行判断就ok,甚至不需要对TreeNode.val进行判断
if root in nodes + [None]:
return root
l_ret = self.lowestCommonAncestor(root.left, nodes)
r_ret = self.lowestCommonAncestor(root.right, nodes)
if l_ret is None and r_ret is None:
return None
if l_ret is not None and r_ret is not None:
return root
return l_ret if l_ret else r_ret
1123. 最深叶节点的最近公共祖先
「一题双解」递归优化
class Solution:
depth = {}
def lcaDeepestLeaves(self, root: TreeNode) -> TreeNode:
self.build_depth(root)
return self.get_lca(root)
def get_lca(self, root):
if root is None:
return None
l_depth = self.get_depth(root.left)
r_depth = self.get_depth(root.right)
if l_depth == r_depth:
return root
elif l_depth < r_depth:
return self.get_lca(root.right)
else:
return self.get_lca(root.left)
def get_depth(self, root):
if root is None:
return 0
else:
return self.depth[root.val]
def build_depth(self, root):
if root is None:
return 0
d = max(self.build_depth(root.left), self.build_depth(root.right)) + 1
self.depth[root.val] = d
return d
class Solution:
def lcaDeepestLeaves(self, root: TreeNode) -> TreeNode:
return self.get_lca(root)[0]
def get_lca(self, root: TreeNode) -> Tuple[TreeNode, int]:
if root is None:
return root, 0
l_ret, l_dep = self.get_lca(root.left)
r_ret, r_dep = self.get_lca(root.right)
ans_dep = max(l_dep, r_dep) + 1
if l_dep == r_dep:
return root, ans_dep
elif l_dep < r_dep:
return r_ret, ans_dep
else:
return l_ret, ans_dep
235. 二叉搜索树的最近公共祖先
面试题68 - I. 二叉搜索树的最近公共祖先(迭代 / 递归,清晰图解)
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
qv = q.val
pv = p.val
while root:
rv = root.val
if rv < qv and rv < pv:
root = root.right
elif rv > qv and rv > pv:
root = root.left
else:
break
return root
114. 二叉树展开为链表
一、两阶段:中序遍历+展开链表 — 时间 O ( N ) O(N) O(N) 空间 O ( N ) O(N) O(N)
class Solution:
def flatten(self, root: TreeNode) -> None:
preorderList = list()
def preorderTraversal(root: TreeNode):
if root:
preorderList.append(root)
preorderTraversal(root.left)
preorderTraversal(root.right)
preorderTraversal(root)
size = len(preorderList)
for i in range(1, size):
prev, curr = preorderList[i - 1], preorderList[i]
prev.left = None
prev.right = curr
二、用栈遍历,同时更新prev结点实现【展开链表】
时间 O ( N ) O(N) O(N) 空间 O ( N ) O(N) O(N) — 空间复杂度来自【栈的空间】
只能用非递归
class Solution:
def flatten(self, root: TreeNode) -> None:
if not root: return
stack = [root]
prev = None
while stack:
curr = stack.pop()
if prev:
prev.left = None
prev.right = curr
left, right = curr.left, curr.right
if right:
stack.append(right)
if left:
stack.append(left)
prev = curr
三、寻找前驱结点
详细通俗的思路分析,多解法
class Solution:
def flatten(self, root: TreeNode) -> None:
if not root:
return None
left, right = root.left, root.right
root.left = None
root.right = left
p = root
while p.right:
p = p.right
p.right = right
self.flatten(root.right)
class Solution:
def flatten(self, root: TreeNode) -> None:
if not root:
return None
self.flatten(root.left)
self.flatten(root.right)
left, right = root.left, root.right
root.left = None
root.right = left
p = root
while p.right:
p = p.right
p.right = right
class Solution:
def flatten(self, root: TreeNode) -> None:
while root:
left, right = root.left, root.right
root.left = None
root.right = left
p = root
while p.right:
p = p.right
p.right = right
root = root.right
897. 递增顺序搜索树
注意一个坑:
class Solution:
def increasingBST(self, root: TreeNode) -> TreeNode:
dummmy = TreeNode(-1)
pre = dummmy
def inorder(root):
nonlocal pre
if not root: return
# left
inorder(root.left)
# travel
if pre:
# 不是 pre.left = None
root.left = None
pre.right = root
pre = root
# right
inorder(root.right)
inorder(root)
return dummmy.right
剑指 Offer 36. 二叉搜索树与双向链表
面试题36. 二叉搜索树与双向链表(中序遍历,清晰图解)
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root: # 记得处理边界条件
return root
pre = None
head = None
def travel(node: Node):
nonlocal pre, head
if node is None:
return
travel(node.left)
if head is None:
# 不设dummy head
head = node
if pre is not None:
pre.right = node
node.left = pre
pre = node
travel(node.right)
travel(root)
pre.right = head # 加了后两句就好了
head.left = pre
return head
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root: return None
stack = []
pre = None
head = None
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
if root:
if head is None:
head = root
if pre is not None:
pre.right = root
root.left = pre
pre = root
root = root.right
pre.right = head
head.left = pre
return head
给一个长度为 N N N的有序序列 S S S,问有多少种不同的BST树结构 { T } \{T\} {T},其中序遍历序列 S ′ = S S^\prime = S S′=S 。(1) 求不同的BST树结构数量 ∣ T ∣ |T| ∣T∣ 。 (2) 求BST树结构集合 { T } \{T\} {T}
generate(start, end)
函数,返回值为序列 S = [ s ⋯ e ] S=[s\cdots e] S=[s⋯e]生成的BST树结构集合 { T } \{T\} {T}。不妨对这个序列做一轮遍历( i = s ⋯ e i=s\cdots e i=s⋯e),用 i i i 构造根节点,左侧序列 [ s ⋯ i − 1 ] [s\cdots i-1] [s⋯i−1]形成一个集合 { T L } \{T_L\} {TL},右侧序列 [ i + 1 ⋯ e ] [i+1\cdots e] [i+1⋯e]形成一个集合 { T R } \{T_R\} {TR},这两个集合做笛卡尔积,分别做根节点的左右子树。递归完成构造96. 不同的二叉搜索树
catalan 数:
C 2 n n n + 1 \frac{C_{2n}^n}{n+1} n+1C2nn
def C(a, b):
ans = 1
for _ in range(b):
ans *= a
a -= 1
while b >= 1:
ans //= b
b -= 1
return ans
def catalan(n):
return C(2*n, n) // (n + 1)
class Solution:
def numTrees(self, n: int) -> int:
return (catalan(n))
95. 不同的二叉搜索树 II
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
def generate(start, end):
if start > end:
return [None, ]
ans = []
for i in range(start, end + 1):
left_nodes = generate(start, i - 1)
right_nodes = generate(i + 1, end)
for left_node in left_nodes:
for right_node in right_nodes:
ans.append(TreeNode(i, left_node, right_node))
return ans
return generate(1, n)
894. 所有可能的满二叉树
class Solution:
memo = {0:[], 1:[TreeNode(0)]}
def allPossibleFBT(self, n: int) -> List[TreeNode]:
if n in self.memo:
return self.memo[n]
ans = []
for x in range(n):
y = n - 1 - x
for left in self.allPossibleFBT(x):
for right in self.allPossibleFBT(y):
root = TreeNode(0)
root.left = left
root.right = right
ans.append(root)
self.memo[n] = ans
return ans
面试题 04.09. 二叉搜索树序列
class Solution:
def BSTSequences(self, root: TreeNode) -> List[List[int]]:
if not root: return [[]]
ans: List[List[int]] = []
def dfs(root, choices, path):
if root.left:
choices.append(root.left)
if root.right:
choices.append(root.right)
if not choices:
ans.append(path)
for i in range(len(choices)):
dfs(choices[i], choices[:i] + choices[i + 1:], path + [choices[i].val])
dfs(root, [], [root.val])
return ans
968. 监控二叉树
[监控二叉树] 后序遍历递归状态 贪心决策 Python
NONE = 0
COVER = 1
CAMERA = 2
class Solution:
def minCameraCover(self, root: TreeNode) -> int:
self.res = 0
def dfs(root):
# 叶子结点默认为NONE
if root is None:
return COVER
left = dfs(root.left)
right = dfs(root.right)
# 如果任一为【无覆盖】NONE,则应该为【摄像头】CAMERA
if left == NONE or right == NONE:
self.res += 1
return CAMERA
# 表面被子结点【覆盖】了
if left == CAMERA or right == CAMERA:
return COVER
# 最经典的叶子结点为【无覆盖】NONE
if left == COVER and right == COVER:
return NONE
if dfs(root) == NONE:
self.res += 1
return self.res