mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
121 lines
3.1 KiB
Plaintext
121 lines
3.1 KiB
Plaintext
---
|
||||
|
|
title: Breadth-First Search
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Breadth-First Search (BFS)
|
|||
|
|
|
|||
|
|
## The idea
|
|||
|
|
|
|||
|
|
You want a mango seller. You ask your friends first.
|
|||
|
|
No luck? You ask friends-of-friends. Then their friends.
|
|||
|
|
You search in **rings**, closest first.
|
|||
|
|
|
|||
|
|
That is BFS. It answers two questions:
|
|||
|
|
1. **Is there a path** from A to B?
|
|||
|
|
2. **What is the shortest path?** (unweighted — fewest hops)
|
|||
|
|
|
|||
|
|
BFS is *always* shortest path on unweighted graphs. First time you reach a node = fewest possible steps.
|
|||
|
|
|
|||
|
|
## The picture
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
YOU((You)) --> A((Alice))
|
|||
|
|
YOU --> B((Bob))
|
|||
|
|
A --> C((Claire))
|
|||
|
|
A --> D((Dan))
|
|||
|
|
B --> E((Eve))
|
|||
|
|
E --> M((Mango seller ✓))
|
|||
|
|
|
|||
|
|
style YOU fill:#1565c0,color:#fff
|
|||
|
|
style A fill:#6a1b9a,color:#fff
|
|||
|
|
style B fill:#6a1b9a,color:#fff
|
|||
|
|
style C fill:#ef6c00,color:#fff
|
|||
|
|
style D fill:#ef6c00,color:#fff
|
|||
|
|
style E fill:#ef6c00,color:#fff
|
|||
|
|
style M fill:#2e7d32,color:#fff
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Blue = start. Purple = ring 1. Orange = ring 2. Green = found at ring 3.
|
|||
|
|
|
|||
|
|
## The two tools
|
|||
|
|
|
|||
|
|
1. **Queue** — first in, first out. This keeps the rings in order.
|
|||
|
|
2. **Visited set** — never check the same node twice. Without it, cycles loop forever.
|
|||
|
|
|
|||
|
|
## The template
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from collections import deque
|
|||
|
|
|
|||
|
|
def bfs(start, graph: dict) -> None:
|
|||
|
|
queue = deque([start])
|
|||
|
|
visited = {start}
|
|||
|
|
|
|||
|
|
while queue:
|
|||
|
|
level_size = len(queue) # snapshot = one full ring
|
|||
|
|
for _ in range(level_size):
|
|||
|
|
node = queue.popleft()
|
|||
|
|
# process node here
|
|||
|
|
for nxt in graph.get(node, []):
|
|||
|
|
if nxt not in visited:
|
|||
|
|
visited.add(nxt) # mark WHEN queued, not when popped
|
|||
|
|
queue.append(nxt)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Multi-source BFS (LC 994 — Rotting Oranges)
|
|||
|
|
|
|||
|
|
Start with *all* rotten oranges in the queue at once.
|
|||
|
|
Each ring = one minute of rot spreading.
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from collections import deque
|
|||
|
|
|
|||
|
|
def oranges_rotting(grid: list[list[int]]) -> int:
|
|||
|
|
rows, cols = len(grid), len(grid[0])
|
|||
|
|
queue = deque()
|
|||
|
|
fresh = 0
|
|||
|
|
|
|||
|
|
for r in range(rows):
|
|||
|
|
for c in range(cols):
|
|||
|
|
if grid[r][c] == 2:
|
|||
|
|
queue.append((r, c))
|
|||
|
|
elif grid[r][c] == 1:
|
|||
|
|
fresh += 1
|
|||
|
|
|
|||
|
|
minutes = 0
|
|||
|
|
dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
|
|||
|
|
|
|||
|
|
while queue and fresh > 0:
|
|||
|
|
for _ in range(len(queue)):
|
|||
|
|
r, c = queue.popleft()
|
|||
|
|
for dr, dc in dirs:
|
|||
|
|
nr, nc = r + dr, c + dc
|
|||
|
|
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
|
|||
|
|
grid[nr][nc] = 2
|
|||
|
|
fresh -= 1
|
|||
|
|
queue.append((nr, nc))
|
|||
|
|
minutes += 1
|
|||
|
|
|
|||
|
|
return minutes if fresh == 0 else -1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Complexity
|
|||
|
|
|
|||
|
|
| | Time | Space |
|
|||
|
|
|---|---|---|
|
|||
|
|
| BFS | O(V + E) | O(V) |
|
|||
|
|
|
|||
|
|
V = nodes, E = edges. On a grid: O(rows × cols).
|
|||
|
|
|
|||
|
|
## Close-out ritual
|
|||
|
|
|
|||
|
|
Before you submit, say out loud:
|
|||
|
|
1. Time and space complexity.
|
|||
|
|
2. One edge case trace: empty graph, start = target, or unreachable target.
|
|||
|
|
|
|||
|
|
## Plan problems
|
|||
|
|
|
|||
|
|
**Tree BFS A-set:** LC 102 · 199 · 1448
|
|||
|
|
**Graph BFS A-set:** LC 994 · 127
|
|||
|
|
**B-set:** LC 103 · 637 · 909 · 433 · 286
|