Post

NEETCODE004: Backtracking / Graphs / Advanced Graphs

NEETCODE004: Backtracking / Graphs / Advanced Graphs

这是 PENGYI_NEETCODE_MAP 的第五篇:

1
NEETCODE004 -> Backtracking / Graphs / Advanced Graphs

这一篇进入搜索和图。

这三类题共同训练的是:

1
状态空间建模。

数组题通常是一维扫描。 树题通常是层级递归。 Backtracking 和 Graph 则更像:

1
在一个可能很大的状态空间里,系统地探索、剪枝、标记、回退、找路径或判断结构。

这和 agent、research planning、Graph RAG、quant strategy search 都高度相关。

一句话模型

1
2
3
Backtracking = DFS over a decision tree with choose/explore/undo.
Graphs = traversal over nodes and edges with visited state.
Advanced Graphs = optimized traversal over weighted or constrained relations.

中文:

1
2
3
Backtracking 训练组合搜索。
Graphs 训练关系网络遍历。
Advanced Graphs 训练加权关系优化。

Backtracking

Backtracking 的核心是:

1
搜索所有可能选择,但在每一步维护当前 path,并在回退时恢复状态。

典型信号:

1
2
3
4
5
6
7
8
all combinations
all permutations
all subsets
all valid boards
word search
n queens
combination sum
generate parentheses

Backtracking 不是普通 DFS 的另一个名字。 它强调:

1
做选择 -> 递归探索 -> 撤销选择

Backtracking 标准模板

1
2
3
4
5
6
7
8
9
10
11
12
def backtrack(path, start):
    if is_solution(path):
        ans.append(path.copy())
        return

    for i in range(start, len(choices)):
        if should_skip(i):
            continue

        path.append(choices[i])
        backtrack(path, next_start)
        path.pop()

三个关键点:

1
2
3
1. path 表示当前已做选择。
2. start 或 visited 控制可选空间。
3. append 后必须 pop,恢复现场。

Backtracking 的几种形态

Subsets

每个元素选或不选:

1
2
decision tree depth = n
branches = include / exclude

或者用 start 循环:

1
2
3
4
5
6
7
def dfs(start):
    ans.append(path.copy())

    for i in range(start, len(nums)):
        path.append(nums[i])
        dfs(i + 1)
        path.pop()

Permutations

每一层选择一个未使用元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def dfs():
    if len(path) == len(nums):
        ans.append(path.copy())
        return

    for i in range(len(nums)):
        if used[i]:
            continue

        used[i] = True
        path.append(nums[i])
        dfs()
        path.pop()
        used[i] = False

Combination Sum

关键是是否允许重复使用:

1
2
允许重复 -> dfs(i)
不允许重复 -> dfs(i + 1)

这就是很多 backtracking 题的核心差异。

Backtracking 常见坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 忘记 path.copy()
    ans 里会引用同一个 path 对象。

2. 忘记撤销选择
    path.pop 或 used[i] = False。

3. 去重逻辑错误
    排序后跳过同层重复,而不是跳过所有重复。

4. start 更新错误
    是否允许重复使用元素要清楚。

5. 剪枝条件太激进
    正确答案被剪掉。

Graphs

Graph 的核心是:

1
node + edge + traversal + visited

典型信号:

1
2
3
4
5
6
7
8
islands
connected components
course schedule
clone graph
walls and gates
rotting oranges
pacific atlantic
valid tree

第一步不是写 BFS/DFS。 第一步是建模:

1
2
3
4
5
6
什么是 node?
什么是 edge?
图是有向还是无向?
是否有权重?
是否需要检测环?
是否需要最短路径?

Graph 表示方式

Adjacency list:

1
2
3
4
5
graph = {node: [] for node in nodes}

for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)  # if undirected

Grid graph:

1
2
3
4
5
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]

for dr, dc in directions:
    nr = r + dr
    nc = c + dc

很多 matrix 题本质是 graph。

1
2
cell = node
neighboring cell = edge

DFS 模板

1
2
3
4
5
6
7
8
def dfs(node):
    if node in visited:
        return

    visited.add(node)

    for nei in graph[node]:
        dfs(nei)

Grid DFS:

1
2
3
4
5
6
7
8
9
10
def dfs(r, c):
    if r < 0 or r == rows or c < 0 or c == cols:
        return
    if grid[r][c] != "1":
        return

    grid[r][c] = "0"

    for dr, dc in directions:
        dfs(r + dr, c + dc)

训练点:

1
visited 必须在进入时标记,避免重复和死循环。

BFS 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from collections import deque

queue = deque([start])
visited = {start}
distance = 0

while queue:
    for _ in range(len(queue)):
        node = queue.popleft()

        for nei in graph[node]:
            if nei not in visited:
                visited.add(nei)
                queue.append(nei)

    distance += 1

BFS 适合:

1
2
3
4
unweighted shortest path
level expansion
multi-source spread
minimum steps

例如 rotting oranges:

1
多个 rotten orange 同时作为 BFS 起点。

这叫 multi-source BFS。

Topological Sort

拓扑排序用于有向无环图:

1
2
3
4
dependencies
prerequisites
course schedule
build order

Kahn 算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections import deque

indegree = {node: 0 for node in nodes}
graph = {node: [] for node in nodes}

for u, v in edges:
    graph[u].append(v)
    indegree[v] += 1

queue = deque([node for node in nodes if indegree[node] == 0])
order = []

while queue:
    node = queue.popleft()
    order.append(node)

    for nei in graph[node]:
        indegree[nei] -= 1
        if indegree[nei] == 0:
            queue.append(nei)

return order if len(order) == len(nodes) else []

核心判断:

1
如果最终无法处理所有节点,说明存在 cycle。

Union Find

Union Find 用于动态连通性。

适合:

1
2
3
4
number of connected components
valid tree
redundant connection
minimum spanning tree

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
parent = list(range(n))
rank = [1] * n

def find(x):
    while x != parent[x]:
        parent[x] = parent[parent[x]]
        x = parent[x]
    return x

def union(a, b):
    ra = find(a)
    rb = find(b)

    if ra == rb:
        return False

    if rank[ra] < rank[rb]:
        parent[ra] = rb
    elif rank[ra] > rank[rb]:
        parent[rb] = ra
    else:
        parent[rb] = ra
        rank[ra] += 1

    return True

训练点:

1
2
3
find 找 root。
union 合并集合。
如果两个节点已经同 root,再 union 就发现环。

Advanced Graphs

Advanced Graphs 主要进入:

1
2
3
4
weighted graph
shortest path
minimum spanning tree
dependency order

关键是选算法。

1
2
3
4
5
6
unweighted shortest path -> BFS
non-negative weighted shortest path -> Dijkstra
negative edge possible -> Bellman-Ford
minimum spanning tree -> Kruskal / Prim
dependency order -> topological sort
dynamic connectivity -> Union Find

这就是 graph 题的 algorithm router。

Dijkstra

Dijkstra 用于:

1
non-negative weighted shortest path

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import heapq

dist = {start: 0}
heap = [(0, start)]

while heap:
    cost, node = heapq.heappop(heap)

    if cost > dist.get(node, float("inf")):
        continue

    for nei, weight in graph[node]:
        new_cost = cost + weight
        if new_cost < dist.get(nei, float("inf")):
            dist[nei] = new_cost
            heapq.heappush(heap, (new_cost, nei))

核心:

1
2
heap 每次弹出当前最短候选。
如果弹出的是旧状态,跳过。

常见坑:

1
存在负权边时不能直接用 Dijkstra。

Bellman-Ford

Bellman-Ford 可以处理负边。

核心思想:

1
对所有边 relax n-1 轮。

适合:

1
2
3
cheapest flights within k stops
negative edge discussion
limited stops path

NeetCode 里常见变体是限制 stops。 这时要注意:

1
每一轮用上一轮 dist 的 copy,避免同一轮内连续使用多条边。

Graph 常见坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. visited 标记时机
    BFS 通常入队时标记,避免重复入队。

2. 有向 / 无向混淆
    edge 是否双向加入。

3. grid 边界
    r/c 是否越界。

4. cycle detection
    有向图和无向图方法不同。

5. topological indegree 方向
    prerequisite -> course,别反。

6. Dijkstra 过期状态
    heap 里可能有旧 cost,要跳过。

7. Union Find 节点编号
    0-index 和 1-index 要统一。

Backtracking 和 Graph 的关系

Backtracking 可以看成:

1
在隐式 decision graph 上 DFS。

Graph traversal 是:

1
在显式 node-edge graph 上 DFS/BFS。

二者都需要:

1
2
3
4
5
state
visited
choice
termination
rollback or traversal discipline

所以这两类题是 agent 思维的基础。

Agent Harness 视角

这类题非常适合测试 agent 的长程推理。

评估点:

1
2
3
4
5
6
7
8
9
10
11
12
13
Backtracking
    是否能定义 state 和 choices。
    是否能撤销状态。
    是否能处理重复解。

Graphs
    是否能建模 node / edge。
    是否正确使用 visited。
    是否判断 BFS / DFS / topo / union find。

Advanced Graphs
    是否能根据权重和约束选择 Dijkstra / Bellman-Ford / MST。
    是否能解释复杂度。

对 coding agent 来说,这类题比数组题更能暴露:

1
2
3
state modeling
algorithm selection
long-horizon consistency

Quant / Research OS 迁移

Graph 能力对我们很重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Graph RAG
    entity / relation / community / path reasoning

Quant
    asset relation graph
    industry chain graph
    risk contagion graph
    lead-lag relation graph

Research OS
    paper citation graph
    idea dependency graph
    experiment lineage graph

Agent
    task graph
    tool call graph
    plan dependency graph

Backtracking 对应:

1
2
3
4
hypothesis search
strategy combination search
parameter search
prompt/action sequence search

所以这一篇不是只为了算法题。 它直接连接我们的 agent 和 research system。

面试表达模板

Backtracking:

1
2
3
4
I model the problem as a decision tree.
At each step, I choose one candidate, recurse into the next state,
and then undo the choice before trying the next candidate.
This systematically explores all valid combinations while allowing pruning.

Graphs:

1
2
3
4
I first define the nodes and edges, then decide whether the graph is directed,
weighted, and whether we need shortest path, connectivity, or cycle detection.
For unweighted shortest path I use BFS; for traversal or components I use DFS/BFS;
for dependencies I use topological sort.

Advanced Graphs:

1
2
3
If all edge weights are non-negative and we need shortest path, Dijkstra is appropriate.
If we need connectivity under dynamic unions, Union Find is appropriate.
If we need a dependency order, topological sorting is the right abstraction.

当前结论

NEETCODE004 的核心结论:

1
2
3
Backtracking 训练隐式搜索树。
Graphs 训练显式关系网络。
Advanced Graphs 训练加权和约束关系优化。

这一组题是从 coding interview 走向 agent / Graph RAG / Research OS 的关键桥梁。

This post is licensed under CC BY 4.0 by the author.