🔄 复习日 / Review Day 95
📊 进度 / Progress
NeetCode: 75/150 · SysDesign: 60/40 · Behavioral: 60/40 · Frontend: 37/50 · AI: 30/30
🔥 Reviewing Days 91–94
今天是复习日!回顾过去4天的内容。
It's review day! Let's consolidate the last 4 days.
回顾主题 / Topics Covered:
- Day 91: Permutations (回溯)
- Day 92: Subsets II (去重回溯)
- Day 93: Combination Sum II (限量回溯)
- Day 94: 深度复习 — Word Search & 2D Grid Backtracking
- Day 91: pytest Fixtures & factory_boy
- Day 92: Property-Based Testing (hypothesis)
- Day 93: Testing Async Code (pytest-asyncio)
📝 Quick Quiz — 3 Mini-Reviews
Q1: [💻 Algorithms — Backtracking]
Permutations vs Subsets II vs Combination Sum II — 这三道题都用回溯,但各有不同。说出它们最核心的区别是什么?
All three use backtracking, but each has a key structural difference. What distinguishes them?
<details><summary>显示答案 / Show Answer</summary>
Permutations(全排列):每个数都要用,顺序重要,用 used[] 数组标记已选元素,不能重复用同一个下标。模板特征:if len(path) == len(nums): result.append(path[:])
Subsets II(含重复的子集):数组有重复元素,需要先排序,然后在同一层跳过重复值:if i > start and nums[i] == nums[i-1]: continue。长度不限,每次递归都收集结果。
Combination Sum II(每个元素只用一次):同样先排序+去重,区别是 start = i + 1(每元素只能用一次),且需要剪枝 if running_sum > target: break。
核心口诀 / Cheat Sheet:
</details>
Q2: [🐍 Python Craft — Testing]
你在用 hypothesis 做属性测试,写了 @given(st.lists(st.integers())) 来测试你的 merge_sort 函数。测试跑了1000次但一次都没找到 bug。这能说明你的代码正确吗?什么情况下 property-based testing 比 example-based testing 更有价值?
You run hypothesis 1000 times with no failures. Does this prove your merge_sort is correct? When does property-based testing shine vs example-based?
<details><summary>显示答案 / Show Answer</summary>
不能证明正确,但提高了信心。 Hypothesis 会收缩(shrink)失败案例找到最小反例,但它不做穷举验证。
Property-based testing 的价值在于:
- 边界盲点:你想不到的输入,hypothesis 能自动生成(空列表、MAX_INT、重复元素、负数)
- 不变量验证:测试"输出长度等于输入长度"、"输出已排序"这类性质,而不是具体值
- 回归覆盖:一旦找到失败案例,hypothesis 会记录在
.hypothesis/文件中,下次永远会重跑
Example-based 更适合:
- 明确业务逻辑("输入A应该返回B")
- 文档式测试,让人一眼看懂期望行为
最佳实践: 两者结合 — example tests 作为规格文档,property tests 作为健壮性保险。
from hypothesis import given, settings
import hypothesis.strategies as st
@given(st.lists(st.integers()))
@settings(max_examples=500)
def test_sort_properties(lst):
result = my_sort(lst)
assert len(result) == len(lst) # 长度不变
assert all(result[i] <= result[i+1] # 已排序
for i in range(len(result)-1))
assert sorted(result) == sorted(lst) # 元素相同
</details>
Q3: [💻 Algorithms — 2D Grid Backtracking]
Word Search(单词搜索)在深度复习中讲到了一个关键优化:原地标记已访问 vs 用 visited 集合。解释为什么原地修改 board[r][c] = '#' 在这里是正确且更优的,以及在什么情况下你 不应该 这样做?
Word Search uses in-place marking (board[r][c] = '#') instead of a visited set. Why is this correct and faster here? When should you NOT do this?
<details><summary>显示答案 / Show Answer</summary>
为什么原地标记有效:
- 回溯时我们会恢复原始值:
board[r][c] = temp,所以不会破坏输入(对当前路径而言) - 避免了每次递归调用的 O(1) set 查找开销(实际上 set 查找也是 O(1),但减少了对象创建/GC 压力)
- 更重要的是:空间从 O(m×n) 降到 O(1)(不计递归栈),在内存敏感场景有意义
def dfs(r, c, k):
if k == len(word): return True
if not (0 <= r < rows and 0 <= c < cols): return False
if board[r][c] != word[k]: return False
temp = board[r][c]
board[r][c] = '#' # 标记访问
found = any(dfs(r+dr, c+dc, k+1) for dr,dc in dirs)
board[r][c] = temp # 回溯恢复
return found
什么时候 不应该 原地修改:
- 函数要保证输入不变:调用方可能依赖原始 board 进行多次独立查询
- 并发场景:多线程同时搜索同一个 board 会数据竞争
- 需要记录完整路径:原地标记只知道"访问过",不知道"第几步到达"
- board 值有业务含义:修改会产生副作用(比如 board 是实时状态)
</details>
💡 复习巩固记忆,螺旋式上升。回溯三兄弟 + 测试三剑客,相互印证才是真正理解。
📅 明天继续新内容!Day 96 coming up 🚀