Day 4 · Topic 2
Graphs
DFS
Connected components, cycle detection, path existence
BFS
Shortest path (unweighted), level order, min steps
Union-Find
Dynamic connectivity, number of components
Topological Sort
Task ordering with prerequisites (DAG)
Medium Number of Islands
INSIGHT: DFS from each unvisited '1'. "Sink" the island (mark as '0') during DFS to avoid revisiting. Count DFS calls = islands.
def numIslands(grid):
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != '1':
return
grid[r][c] = '0' # sink
dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1)
count = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1; dfs(r, c)
return count Medium Course Schedule I & II (Topological Sort)
INSIGHT: DFS with 3 states: 0=unvisited, 1=visiting (in current path), 2=done. If we visit a node with state=1 → cycle. Add to result AFTER all children processed (postorder).
def findOrder(numCourses, prerequisites):
graph = [[] for _ in range(numCourses)]
for a, b in prerequisites:
graph[b].append(a)
state = [0] * numCourses
result = []
def dfs(node):
if state[node] == 1: return False # cycle
if state[node] == 2: return True # already done
state[node] = 1
for nb in graph[node]:
if not dfs(nb): return False
state[node] = 2
result.append(node) # add after all deps
return True
if not all(dfs(i) for i in range(numCourses)):
return []
return result[::-1] # reverse postorder = topological order Medium Rotting Oranges (Multi-source BFS)
INSIGHT: Multi-source BFS: start with ALL rotten oranges in queue simultaneously. BFS naturally gives shortest time (all spread at same rate).
from collections import deque
def orangesRotting(grid):
rows, cols = len(grid), len(grid[0])
fresh = 0; queue = deque()
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2: queue.append((r, c, 0))
elif grid[r][c] == 1: fresh += 1
max_time = 0
while queue:
r, c, t = queue.popleft()
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 grid[nr][nc] == 1:
grid[nr][nc] = 2; fresh -= 1
max_time = max(max_time, t+1)
queue.append((nr, nc, t+1))
return max_time if fresh == 0 else -1 Hard Word Ladder
INSIGHT: BFS for shortest path. Pre-build pattern→words map ("h*t" → ["hot","hat"]). O(1) neighbor lookup instead of O(n·L) brute force.
from collections import deque, defaultdict
def ladderLength(beginWord, endWord, wordList):
word_set = set(wordList)
if endWord not in word_set: return 0
patterns = defaultdict(list)
for w in wordList:
for i in range(len(w)):
patterns[w[:i] + '*' + w[i+1:]].append(w)
queue = deque([(beginWord, 1)])
visited = {beginWord}
while queue:
word, steps = queue.popleft()
if word == endWord: return steps
for i in range(len(word)):
for nb in patterns[word[:i] + '*' + word[i+1:]]:
if nb not in visited:
visited.add(nb)
queue.append((nb, steps+1))
return 0