1. LeetCode Hot100 96-100 题解精要
这五道题目在算法面试中出现频率极高,主要考察动态规划、回溯算法和数学推导能力。作为面试准备的关键环节,掌握这些题目的核心思路比单纯记忆解法更重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目96:不同的二叉搜索树
2.1 问题本质分析
给定整数n,求由值1到n组成的互不相同的二叉搜索树(BST)数量。这道题需要理解BST的结构特性:对于任意节点,左子树所有节点值小于它,右子树所有节点值大于它。
2.2 动态规划解法
定义dp[n]表示n个节点能组成的BST数量。对于n=0(空树)和n=1(单节点)的情况:
- dp[0] = 1
- dp[1] = 1
递推关系推导:
当有i个节点时,选择第j个节点作为根节点:
- 左子树包含j-1个节点
- 右子树包含i-j个节点
- 组合数为dp[j-1] * dp[i-j]
因此状态转移方程为:
dp[i] = Σ(dp[j-1] * dp[i-j]),j从1到i
2.3 实现代码
python复制def numTrees(n: int) -> int:
dp = [0] * (n + 1)
dp[0], dp[1] = 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]
2.4 复杂度分析
- 时间复杂度:O(n²),双重循环
- 空间复杂度:O(n),dp数组存储
注意:这道题实际上是卡塔兰数(Catalan Number)的应用,了解数学背景有助于快速解题。
3. 题目97:交错字符串
3.1 问题描述验证
给定字符串s1、s2、s3,判断s3是否能由s1和s2交错组成。例如:
s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" → True
3.2 动态规划状态定义
定义dp[i][j]表示s1前i个字符和s2前j个字符能否组成s3前i+j个字符。
边界条件:
- dp[0][0] = True
- dp[i][0] = (s1[:i] == s3[:i])
- dp[0][j] = (s2[:j] == s3[:j])
3.3 状态转移方程
dp[i][j] =
(dp[i-1][j] and s1[i-1] == s3[i+j-1]) or
(dp[i][j-1] and s2[j-1] == s3[i+j-1])
3.4 代码实现
python复制def isInterleave(s1: str, s2: str, s3: str) -> bool:
m, n = len(s1), len(s2)
if m + n != len(s3):
return False
dp = [[False]*(n+1) for _ in range(m+1)]
dp[0][0] = True
for i in range(1, m+1):
dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
for j in range(1, n+1):
dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
for i in range(1, m+1):
for j in range(1, n+1):
dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or \
(dp[i][j-1] and s2[j-1] == s3[i+j-1])
return dp[m][n]
3.5 空间优化技巧
可以使用一维数组优化空间复杂度到O(n):
python复制def isInterleave(s1: str, s2: str, s3: str) -> bool:
m, n = len(s1), len(s2)
if m + n != len(s3):
return False
dp = [False]*(n+1)
dp[0] = True
for j in range(1, n+1):
dp[j] = dp[j-1] and s2[j-1] == s3[j-1]
for i in range(1, m+1):
dp[0] = dp[0] and s1[i-1] == s3[i-1]
for j in range(1, n+1):
dp[j] = (dp[j] and s1[i-1] == s3[i+j-1]) or \
(dp[j-1] and s2[j-1] == s3[i+j-1])
return dp[n]
4. 题目98:验证二叉搜索树
4.1 递归解法
利用BST的性质:节点的值应该在(min_val, max_val)范围内,这个范围会随着遍历过程动态变化。
python复制def isValidBST(root: TreeNode) -> bool:
def helper(node, lower=float('-inf'), upper=float('inf')):
if not node:
return True
val = node.val
if val <= lower or val >= upper:
return False
return helper(node.left, lower, val) and helper(node.right, val, upper)
return helper(root)
4.2 中序遍历解法
BST的中序遍历结果应该是严格递增的序列:
python复制def isValidBST(root: TreeNode) -> bool:
stack, prev = [], float('-inf')
while stack or root:
while root:
stack.append(root)
root = root.left
root = stack.pop()
if root.val <= prev:
return False
prev = root.val
root = root.right
return True
4.3 常见错误
- 仅检查当前节点与左右子节点的关系是不够的,需要维护全局的上下界
- 处理边界值时注意使用float('-inf')和float('inf')
5. 题目99:恢复二叉搜索树
5.1 问题分析
BST中有两个节点被错误交换,需要在不改变结构的情况下恢复。
5.2 Morris中序遍历
空间复杂度O(1)的解法:
python复制def recoverTree(root: TreeNode) -> None:
x = y = pred = None
curr = root
while curr:
if curr.left:
# 找前驱节点
predecessor = curr.left
while predecessor.right and predecessor.right != curr:
predecessor = predecessor.right
if not predecessor.right:
predecessor.right = curr
curr = curr.left
else:
# 检查逆序对
if pred and curr.val < pred.val:
y = curr
if not x:
x = pred
pred = curr
predecessor.right = None
curr = curr.right
else:
if pred and curr.val < pred.val:
y = curr
if not x:
x = pred
pred = curr
curr = curr.right
x.val, y.val = y.val, x.val
5.3 常规中序遍历解法
python复制def recoverTree(root: TreeNode) -> None:
stack = []
x = y = pred = None
curr = root
while stack or curr:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
if pred and curr.val < pred.val:
y = curr
if not x:
x = pred
else:
break
pred = curr
curr = curr.right
x.val, y.val = y.val, x.val
6. 题目100:相同的树
6.1 递归解法
python复制def isSameTree(p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
if not p or not q:
return False
return p.val == q.val and isSameTree(p.left, q.left) and isSameTree(p.right, q.right)
6.2 迭代解法
使用队列进行层序遍历:
python复制from collections import deque
def isSameTree(p: TreeNode, q: TreeNode) -> bool:
queue = deque([(p, q)])
while queue:
n1, n2 = queue.popleft()
if not n1 and not n2:
continue
if not n1 or not n2 or n1.val != n2.val:
return False
queue.append((n1.left, n2.left))
queue.append((n1.right, n2.right))
return True
7. 解题技巧总结
7.1 动态规划要点
- 明确dp数组定义
- 确定初始状态
- 推导状态转移方程
- 考虑空间优化可能性
7.2 树问题处理技巧
- 递归三要素:终止条件、当前处理、递归调用
- 中序遍历对BST特别有效
- Morris遍历实现O(1)空间复杂度
7.3 调试建议
- 先手动模拟小规模测试用例
- 检查边界条件(空树、单节点等)
- 使用print或调试器跟踪关键变量
在实际面试中,建议先明确问题要求,与面试官确认理解正确,然后从暴力解法开始,逐步优化。解释清楚思路比直接写最优解更重要。
