← 2026-07-17 📂 All Days 2026-07-19 →

🏗️ System Design — Synthesis (Expert Phase, Day 94)

Saturday — Deep Dive day. System Design section is in synthesis mode (all 60 curated topics exhausted).

Today's Synthesis Note

This Saturday's content is focused on the Word Search deep dive (algorithms). On synthesis Saturdays for System Design, reflect on cross-cutting architectural patterns covered so far:

Key systems covered (Days 1–93):

  • Distributed systems: Consistent Hashing, Raft consensus, CAP theorem
  • Storage: Key-Value Store, Time-Series DB, Distributed File System (HDFS)
  • Real-time: Chat (WhatsApp), Live Streaming (Twitch), Collaborative Editor (Google Docs)
  • Serving: CDN, API Gateway, Rate Limiter as Service
  • Data pipelines: Kafka-based event streaming, ELK logging

Synthesis question to ponder:

How does a real-world system like GitHub combine: distributed file storage (HDFS-like), content-addressable hashing (consistent hashing ideas), CDN (static asset delivery), real-time (PR review notifications), and geospatial-aware routing (anycast CDN)?

See the Saturday Deep Dive (./2026-07-18-deepdive.md) for today's main content.

🧮 Word Search (LeetCode #79) — Backtracking Deep Dive

Note: This is a Saturday. The full content for this topic is in the Saturday Deep Dive (./2026-07-18-deepdive.md). This file is a reference stub.

Quick Summary

Problem: Given an m×n board of characters, determine if a given word exists — formed from sequentially adjacent cells (horizontal or vertical), where each cell may only be used once.

Pattern: Backtracking — mark cell as visited ('#'), recurse, restore.

def exist(board, word):
    rows, cols = len(board), len(board[0])
    def dfs(r, c, idx):
        if idx == len(word): return True
        if r < 0 or r >= rows or c < 0 or c >= cols: return False
        if board[r][c] != word[idx]: return False
        temp, board[r][c] = board[r][c], '#'
        found = (dfs(r+1,c,idx+1) or dfs(r-1,c,idx+1) or
                 dfs(r,c+1,idx+1) or dfs(r,c-1,idx+1))
        board[r][c] = temp
        return found
    for r in range(rows):
        for c in range(cols):
            if dfs(r, c, 0): return True
    return False

Complexity: Time O(m×n×4^L), Space O(L) (call stack)

🔗 LeetCode #79

📹 NeetCode

Block progress: 6/9 in Backtracking block. Next: Palindrome Partitioning (#131).

🗣️ Soft Skills — Synthesis (Expert Phase, Day 94)

Saturday — Deep Dive day. Soft skills section is in synthesis mode.

Synthesis Reflection: Senior+ Communication Patterns

After covering 60 behavioral questions, the meta-pattern for senior/staff engineers:

The STAR+ Framework (STAR with Systemic Thinking):

  • Situation: Set scale context (millions of users, 10-team org, $X impact)
  • Task: Clarify your specific role vs. the team's
  • Action: Show trade-off reasoning, not just execution
  • Result: Quantify, then reflect on what you'd do differently
  • +Systemic: How did this change your team/org beyond the immediate outcome?

Senior-level differentiation signals:

  1. You talk about influencing without authority (cross-team impact)
  2. You show proactive problem discovery (not just firefighting)
  3. You discuss building systems not just solving instances (runbook, postmortem process)
  4. You mention mentoring/multiplying others as part of your impact
  5. You're honest about failure — and what the failure taught the org

Saturday reflection prompt: Think of your most recent engineering decision. Can you narrate it using STAR+? What was the systemic change it drove?

See the Saturday Deep Dive (./2026-07-18-deepdive.md) for today's main content.

🤖 AI — Synthesis (Expert Phase, Day 94)

Saturday — Deep Dive day. AI section is in synthesis mode (all 30 curated topics exhausted).

Today's Synthesis Note

Today's focus is the Word Search deep dive (backtracking on 2D grids). On synthesis Saturdays for AI, reflect on cross-cutting themes from Days 1–93:

Key AI topics covered:

  • Fundamentals: Transformers, attention, tokenization, embeddings
  • Training/Tuning: RLHF, LoRA/QLoRA, RLAIF, knowledge distillation, synthetic data
  • Serving: Inference optimization (KV cache, speculative decoding), quantization, MoE
  • Applications: RAG, agents (ReAct, tool use), MCP, function calling, structured output
  • Landscape: Open vs closed source, AGI debates, multimodal, diffusion, coding assistants
  • Ops: Evaluation, monitoring, guardrails, cost management, context windows

Cross-cutting connection to today's algorithm topic:

Backtracking + pruning is directly analogous to how LLM agents explore tool-call plans. When an agent uses ReAct-style reasoning, each "thought → action → observation" is a DFS choice; failing branches get pruned. Word Search's frequency pre-check maps onto how well-designed agents refuse impossible tasks up front instead of wasting inference tokens.

Synthesis question to ponder:

How would you design an LLM-driven "structured search agent" that decides when to backtrack vs commit? What signal would tell it a branch is dead-end?

See the Saturday Deep Dive (./2026-07-18-deepdive.md) for today's main content.

Attention Heatmap

When the model processes it, it attends most strongly to animal.

Theanimaldidntcrossthestreetbecauseitwastootired
it →

🔬 Saturday Deep Dive: 回溯算法与二维网格搜索 — Word Search & Beyond (18 min read)

📊 NeetCode: 75/150 · SysDesign: 60/40✅ · Behavioral: 60/40✅ · Frontend: 37/50 · AI: 30/30✅

🔥 Day 94 — Expert Phase


Overview / 概述

中文:

回溯(Backtracking)是算法面试中最高频的模式之一。它的核心思想极其简单:**选择 → 递归 → 撤销**。但"简单"不等于"容易"——面试中真正拉开差距的,是如何剪枝、如何把模板适配到具体问题、以及如何分析复杂度。

今天我们以 Word Search(LC #79) 为核心,深挖回溯在二维网格中的完整实现,然后拓展到:

  • 为什么朴素 DFS 会超时,如何剪枝
  • 访问标记的 3 种写法(in-place vs visited set vs bitmask)
  • Word Search II(多词同时搜索)如何用 Trie 优化
  • N-Queens 如何把同一模板推向 Hard 题
  • 真实生产场景中的回溯思想

English:

Backtracking is one of the highest-frequency algorithm patterns in tech interviews. The core idea fits in one line: **choose → recurse → undo**. But what separates candidates is knowing how to prune, how to adapt the template, and how to reason about complexity.

We'll use Word Search (LC #79) as our anchor problem, then expand outward to pruning strategies, Trie-based multi-word search, N-Queens, and real production use cases.


Part 1: Theory / 理论基础 (5 min)

回溯 = 带撤销的 DFS / Backtracking = DFS with Undo

中文:

回溯算法本质上是在一棵隐式决策树上做 DFS。每个节点代表一个"当前状态",每条边代表一个"选择"。当走到叶节点时得到一个完整解;当当前路径不可能产生有效解时,剪枝(prune)——直接返回,避免探索子树。

                    [开始]
                  /    |    \
              选A      选B      选C
             / \      / \      / \
           ...  ...  ...  ... ...  ...

关键性质:

  1. 状态空间树:解的集合可以组织成一棵树
  2. 约束传播:边界条件越早检查,剪掉的子树越多
  3. 撤销(undo)必须精确:修改什么,撤销什么——不多不少

English:

Backtracking explores an implicit decision tree. At each node, you make a choice, recurse deeper, then undo that choice before trying the next option. The power comes from pruning: if the current partial state can never lead to a valid solution, skip the entire subtree.

Three mental models that help:

  • State space tree: visualize all possible choices as a tree
  • Constraint propagation: check constraints early, not at the leaf
  • Exact undo: match every mutation with its precise reversal

Word Search 问题定义

Grid: A B C E S F C S A D E E word = "ABCCED" → True word = "SEE" → True word = "ABCB" → False (can't reuse cells)

搜索规则:

  • 从任意格子出发
  • 每步只能走上下左右(4方向)
  • 同一路径中不能重复使用同一格子
  • 找到一条路径使得连起来的字母等于 word 即返回 True

Part 2: Step-by-Step Implementation / 一步一步实现 (8 min)

方案一:朴素回溯(Naive Backtracking)

中文:先写能跑的,再优化。

class Solution:
    def exist(self, board: list[list[str]], word: str) -> bool:
        rows, cols = len(board), len(board[0])
        
        def dfs(r: int, c: int, idx: int) -> bool:
            # Base case: matched all characters
            if idx == len(word):
                return True
            
            # Boundary + character check — prune early!
            if (r < 0 or r >= rows or
                c < 0 or c >= cols or
                board[r][c] != word[idx]):
                return False
            
            # Mark as visited by temporarily overwriting
            # This is the "in-place" trick: no extra set needed
            temp = board[r][c]
            board[r][c] = '#'   # 标记已访问 / mark visited
            
            # Explore all 4 directions
            found = (
                dfs(r + 1, c, idx + 1) or
                dfs(r - 1, c, idx + 1) or
                dfs(r, c + 1, idx + 1) or
                dfs(r, c - 1, idx + 1)
            )
            
            # Undo the mark — this is the "backtrack" step
            board[r][c] = temp  # 撤销标记 / restore
            
            return found
        
        # Try every cell as the starting point
        for r in range(rows):
            for c in range(cols):
                if dfs(r, c, 0):
                    return True
        return False

复杂度 / Complexity:

  • Time: O(M × N × 4^L) — M×N starting cells, each path up to 4^L branches
  • Space: O(L) — recursion stack depth equals word length

L is len(word). For a 6×6 board and 10-char word, this is 36 × 4^10 ≈ 36M ops — feasible.


方案二:关键剪枝优化

中文:4 个剪枝,每一个都有实际效果。

class Solution:
    def exist(self, board: list[list[str]], word: str) -> bool:
        rows, cols = len(board), len(board[0])
        
        # ============================================================
        # PRUNING #1: Frequency check — O(M*N + L), saves huge time
        # 如果 board 里某个字母的数量 < word 里需要的数量,直接返回 False
        # ============================================================
        from collections import Counter
        board_count = Counter(c for row in board for c in row)
        word_count  = Counter(word)
        for ch, cnt in word_count.items():
            if board_count[ch] < cnt:
                return False
        
        # ============================================================
        # PRUNING #2: Reverse word if tail is rarer than head
        # 从频率更低的一端开始搜索 → 更快剪枝
        # Example: word="AAAAAB" → start from 'B' side
        # ============================================================
        if board_count[word[0]] > board_count[word[-1]]:
            word = word[::-1]
        
        def dfs(r: int, c: int, idx: int) -> bool:
            if idx == len(word):
                return True
            if (r < 0 or r >= rows or
                c < 0 or c >= cols or
                board[r][c] != word[idx]):
                return False
            
            temp = board[r][c]
            board[r][c] = '#'
            
            # ============================================================
            # PRUNING #3: Short-circuit with 'or' — stops at first True
            # Python 'or' short-circuits: once one branch returns True,
            # the remaining branches are not evaluated
            # ============================================================
            found = (
                dfs(r + 1, c, idx + 1) or
                dfs(r - 1, c, idx + 1) or
                dfs(r, c + 1, idx + 1) or
                dfs(r, c - 1, idx + 1)
            )
            
            board[r][c] = temp
            return found
        
        for r in range(rows):
            for c in range(cols):
                # ============================================================
                # PRUNING #4: Skip starting cells that don't match word[0]
                # (implicit in dfs check, but can also be explicit)
                # ============================================================
                if dfs(r, c, 0):
                    return True
        return False

方案三:3 种访问标记写法对比

中文:in-place 改写最省空间,但需要保证撤销正确。

# Option A: In-place overwrite (推荐 / Recommended)
# Pros: O(1) extra space, clean
# Cons: board is mutated during search (not thread-safe)
temp = board[r][c]
board[r][c] = '#'
# ... recurse ...
board[r][c] = temp

# Option B: visited set
# Pros: board unchanged, intuitive
# Cons: O(M*N) extra space, set add/remove overhead
visited = set()
visited.add((r, c))
# ... recurse ...
visited.remove((r, c))

# Option C: bitmask (for very small grids, e.g., 4x4)
# Pros: O(1) space, fast bitwise ops
# Cons: only works when M*N ≤ 64
mask |= (1 << (r * cols + c))
# ... recurse ...
mask ^= (1 << (r * cols + c))  # XOR to unset

面试中:用 in-place overwrite,解释清楚即可。


Word Search II — Trie 优化多词搜索

中文:如果要同时搜索 words 列表中的所有词,朴素方法是对每个词跑一遍 DFS = O(W × M × N × 4^L)。用 Trie 可以把 W 个词的搜索合并成一次 DFS。

class TrieNode:
    def __init__(self):
        self.children: dict[str, 'TrieNode'] = {}
        self.word: str | None = None  # store complete word at leaf

class Solution:
    def findWords(self, board: list[list[str]], words: list[str]) -> list[str]:
        # Build Trie from all words
        root = TrieNode()
        for w in words:
            node = root
            for ch in w:
                node = node.children.setdefault(ch, TrieNode())
            node.word = w  # mark end of word
        
        rows, cols = len(board), len(board[0])
        result = []
        
        def dfs(r: int, c: int, node: TrieNode) -> None:
            ch = board[r][c]
            
            # KEY: check if this character exists in Trie
            # If not, prune entire subtree — this is the Trie speedup
            if ch not in node.children:
                return
            
            next_node = node.children[ch]
            
            # Found a complete word
            if next_node.word:
                result.append(next_node.word)
                next_node.word = None  # de-duplicate: don't add same word twice
            
            # Mark visited
            board[r][c] = '#'
            
            for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
                nr, nc = r + dr, c + dc
                if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != '#':
                    dfs(nr, nc, next_node)
            
            board[r][c] = ch  # restore
            
            # Optimization: prune empty Trie nodes (leaf pruning)
            # If next_node has no more children and no word,
            # remove it from parent to speed up future searches
            if not next_node.children and not next_node.word:
                del node.children[ch]
        
        for r in range(rows):
            for c in range(cols):
                dfs(r, c, root)
        
        return result

Trie 优化效果:

  • Without Trie: O(W × M × N × 4^L) — one full search per word
  • With Trie: O(M × N × 4^L_max) — one search for all words simultaneously
  • 关键:Trie 的前缀共享让多个词的公共前缀只走一次

N-Queens — 回溯模板的 Hard 变体

中文:N-Queens 是同一个回溯模板,但"约束检查"更复杂。

class Solution:
    def solveNQueens(self, n: int) -> list[list[str]]:
        result = []
        
        # Track which columns, diagonals are under attack
        # 用 set 追踪哪些列、对角线、反对角线已被攻击
        cols    = set()
        diag    = set()   # r - c is constant along a diagonal
        anti    = set()   # r + c is constant along anti-diagonal
        
        board = [['.'] * n for _ in range(n)]
        
        def backtrack(row: int) -> None:
            # Base case: placed queens in all n rows
            if row == n:
                result.append([''.join(r) for r in board])
                return
            
            for col in range(n):
                # Constraint check — prune invalid placements
                if col in cols or (row - col) in diag or (row + col) in anti:
                    continue
                
                # Choose
                cols.add(col)
                diag.add(row - col)
                anti.add(row + col)
                board[row][col] = 'Q'
                
                # Recurse
                backtrack(row + 1)
                
                # Undo
                cols.discard(col)
                diag.discard(row - col)
                anti.discard(row + col)
                board[row][col] = '.'
        
        backtrack(0)
        return result

对角线技巧 / Diagonal Trick:

For any cell (r, c):
  - Same diagonal:       r - c = constant
  - Same anti-diagonal:  r + c = constant

Example (4x4):
r-c diagonals:       r+c anti-diagonals:
-3 -2 -1  0          0  1  2  3
-2 -1  0  1          1  2  3  4
-1  0  1  2          2  3  4  5
 0  1  2  3          3  4  5  6

Part 3: Edge Cases & Gotchas / 边界情况 (2 min)

中文 & English:

# ❌ Gotcha 1: Forgetting to restore board[r][c]
# 最常见错误:忘记撤销 in-place 标记
# If you mark '#' but don't restore, later DFS branches see '#'
board[r][c] = '#'
result = dfs(...)
# MUST restore before returning!
board[r][c] = temp  # ← don't forget this

# ❌ Gotcha 2: Off-by-one in base case
# idx == len(word) means we matched ALL characters
# Some people write idx == len(word) - 1 → misses last char
if idx == len(word):   # ✅ correct
    return True
if idx == len(word) - 1 and board[r][c] == word[idx]:  # ✅ also correct
    return True

# ❌ Gotcha 3: Word Search II — duplicate results
# If the same word can be found via multiple paths,
# de-duplicate by clearing node.word after first find
if next_node.word:
    result.append(next_node.word)
    next_node.word = None  # ← prevent duplicates

# ❌ Gotcha 4: Empty board / single cell
board = [["a"]]
word = "a"   # → True (idx hits len(word) immediately after matching)
word = "aa"  # → False (can't reuse cell, no neighbors)

# ❌ Gotcha 5: N-Queens — using list for cols check
# O(n) lookup per placement → O(n^3) total
# Use set for O(1) lookup → O(n^2) total

Part 4: Real-World Application / 实际应用 (2 min)

中文: 回溯思想在生产代码里无处不在,只是换了个名字:

  1. 正则引擎(Regex Engine)

- re.match("a*b", "aab") 内部就是回溯 NFA

- 著名的"灾难性回溯"(Catastrophic Backtracking):(a+)+b 匹配 "aaaa..." 导致指数级爆炸

- 解法:使用 re2 引擎(Google),保证 O(n) 线性时间

  1. SAT/约束求解(Constraint Solving)

- Sudoku solver、日程安排、电路布局验证

- 工业级 SAT solver(Minisat、Z3)底层是 DPLL 算法 = 回溯 + 单位传播剪枝

  1. 包依赖解析(Dependency Resolution)

- pip、npm、cargo 的版本解析本质是约束满足

- Cargo(Rust)用 pubgrub 算法,是 DPLL 的变体

  1. 游戏 AI

- 国际象棋、围棋的 Alpha-Beta 剪枝 = 带剪枝的回溯在博弈树上的应用

English: Backtracking shows up constantly in production:

  • Regex engines use NFA-based backtracking (careful: catastrophic backtracking is a real DoS vector)
  • Package managers resolve version constraints via backtracking solvers
  • Database query optimizers explore join orderings with pruning

Part 5: Interview Simulation / 面试模拟 (3 min)

中文: 以下是面试官最常追问的 5 个问题,附简要答案:


Q1: "Word Search 的时间复杂度是多少?"

O(M × N × 4^L),其中 M×N 是 board 大小,L 是 word 长度。每个起点最多展开一棵深度为 L 的四叉树。实际中剪枝大幅降低常数。

A: O(M × N × 4^L) — M×N starting points, each branching up to 4 ways per character, depth L. In practice pruning cuts this significantly.


Q2: "为什么不用 visited set,而是直接改 board?"

In-place 改写省去了 O(M×N) 的额外空间,也避免了 set 的 hash 开销。代价是:board 在 DFS 期间被临时修改,需要保证撤销正确。如果 board 是只读的(比如 const&),则必须用 visited set。

A: In-place saves O(M×N) space and avoids hash overhead. Trade-off: board is temporarily mutated — requires precise undo. Use visited set if board must remain immutable (e.g., multi-threaded search).


Q3: "如何优化 Word Search II 到让 1000 个词同时搜索也不超时?"

1. 用 Trie 合并所有词的搜索 → 从 O(W × M × N × 4^L) 降到 O(M × N × 4^L_max)
2. 找到一个词后立即从 Trie 中删除(leaf pruning)→ 避免重复找已找到的词
3. 频率剪枝:board 里没有某字母就不加入 Trie

A: Trie merges all word searches into one DFS pass. Add leaf pruning (delete matched word's node) to prevent re-visiting already-found words. Frequency pre-filter eliminates words with impossible characters.


Q4: "回溯和动态规划有什么区别?什么时候用哪个?"

回溯:需要所有解,或者问题有路径约束(不能复用元素),用回溯。
DP:只需要最优解或计数,子问题有重叠,用 DP。
本质区别:DP 记忆化子问题结果;回溯不记忆,靠剪枝。

A: Use backtracking when you need all solutions or have path constraints (can't reuse elements). Use DP when you need the optimal/count and subproblems overlap. Backtracking explores; DP remembers.


Q5: "N-Queens 的 r-c 对角线技巧能解释一下吗?"

在棋盘上,同一对角线(左上到右下)上的所有格子,row - col 值相同。同一反对角线上所有格子,row + col 值相同。所以用两个 set 分别存 r-c 和 r+c,O(1) 判断是否有冲突,比扫描整个 board O(n) 快得多。

A: On any diagonal (top-left to bottom-right), all cells share the same r - c value. On any anti-diagonal, all cells share r + c. Store these in sets for O(1) conflict checks instead of scanning the board each time.


🔗 Resources / 参考资料

  • 🔗 LeetCode #79 Word Search: https://leetcode.com/problems/word-search/
  • 📹 NeetCode Word Search: https://www.youtube.com/watch?v=pfiQ_PS1g8E
  • 🔗 LeetCode #212 Word Search II: https://leetcode.com/problems/word-search-ii/
  • 📹 NeetCode Word Search II: https://www.youtube.com/watch?v=asbcE9mZz_U
  • 🔗 LeetCode #51 N-Queens: https://leetcode.com/problems/n-queens/
  • 📹 NeetCode N-Queens: https://www.youtube.com/watch?v=Ph95IHmRp5M
  • 📖 Backtracking template cheat sheet: https://leetcode.com/explore/learn/card/recursion-ii/472/backtracking/