Graphs
Phase 1 28 solutionsGraphs
mindmap
root((Graphs))
Grid BFS/DFS
Island Perimeter LC463
Number of Islands LC200
Max Area of Island LC695
Surrounded Regions LC130
Clone Graph LC133
Topological Sort
Course Schedule LC207
Course Schedule II LC210
Course Schedule IV LC1462
Minimum Height Trees LC310
Verifying Alien Dictionary LC953
Union-Find
Graph Valid Tree LC261
Number of Connected Components LC323
Redundant Connection LC684
Accounts Merge LC721
Multi-Source BFS
Walls and Gates LC286
Rotting Oranges LC994
Pacific Atlantic Water Flow LC417
Implicit Graph
Open the Lock LC752
Word Ladder LC127
Word Ladder II LC126
Evaluate Division LC399
Find Town Judge LC997
Union-Find
Number of Provinces LC547
Bipartite
Bipartite Graph LC785
Grid BFS/DFS
Flood Fill LC733
Number of Enclaves LC1020
Distance of Nearest Cell LC542
Topological Sort
Find Eventual Safe States LC802
Problem List
| # | Problem | LC # | Difficulty | Key Technique |
|---|---|---|---|---|
| 1 | Island Perimeter | 463 | Easy | Grid traversal |
| 2 | Verifying Alien Dictionary | 953 | Easy | Order mapping |
| 3 | Find Town Judge | 997 | Easy | In/out degree |
| 4 | Number of Islands | 200 | Medium | DFS/BFS/Union-Find |
| 5 | Max Area of Island | 695 | Medium | DFS |
| 6 | Clone Graph | 133 | Medium | DFS + hashmap |
| 7 | Walls and Gates | 286 | Medium | Multi-source BFS |
| 8 | Rotting Oranges | 994 | Medium | Multi-source BFS |
| 9 | Pacific Atlantic Water Flow | 417 | Medium | Multi-source BFS |
| 10 | Surrounded Regions | 130 | Medium | DFS from border |
| 11 | Open the Lock | 752 | Medium | BFS shortest path |
| 12 | Course Schedule | 207 | Medium | Topological sort / cycle detection |
| 13 | Course Schedule II | 210 | Medium | Topological sort (Kahn's) |
| 14 | Graph Valid Tree | 261 | Medium | Union-Find / DFS |
| 15 | Course Schedule IV | 1462 | Medium | Topological sort + reachability |
| 16 | Number of Connected Components | 323 | Medium | Union-Find |
| 17 | Redundant Connection | 684 | Medium | Union-Find |
| 18 | Accounts Merge | 721 | Medium | Union-Find |
| 19 | Evaluate Division | 399 | Medium | Weighted DFS/BFS |
| 20 | Minimum Height Trees | 310 | Medium | Topological sort (leaf trimming) |
| 21 | Word Ladder | 127 | Hard | BFS shortest path |
| 22 | Number of Provinces | 547 | Medium | Union-Find / DFS |
| 23 | Flood Fill | 733 | Easy | Grid DFS |
| 24 | Bipartite Graph | 785 | Medium | BFS/DFS coloring |
| 25 | Number of Enclaves | 1020 | Medium | Grid DFS from border |
| 26 | Distance of Nearest Cell (01 Matrix) | 542 | Medium | Multi-source BFS |
| 27 | Word Ladder II | 126 | Hard | BFS + backtrack |
| 28 | Find Eventual Safe States | 802 | Medium | Topological sort / reverse DFS |
Patterns & Diagrams
1. Graph Traversal Decision Tree
flowchart TD
A[Graph Problem] --> B{Need shortest path?}
B -->|Yes, unweighted| C[BFS]
B -->|No| D{"Need connected components<br/>or cycle detection?"}
D -->|Yes, dynamic union ops| E[Union-Find]
D -->|No| F{"Directed graph<br/>with ordering?"}
F -->|Yes| G[Topological Sort]
F -->|No| H{Grid problem?}
H -->|Yes| I{Explore all reachable?}
I -->|Yes| J[DFS - simpler stack]
I -->|No, level-by-level| K[BFS]
H -->|No, adjacency list| L{Explore all paths?}
L -->|Yes| M[DFS + backtrack]
L -->|No| N[BFS]
2. Grid BFS/DFS Pattern
flowchart TD
A[Grid Problem] --> B["Define directions<br/>4-dir or 8-dir"]
B --> C{Multi-source start?}
C -->|Yes - Walls&Gates, Rotting| D["Enqueue ALL sources at once<br/>before BFS loop"]
C -->|No - single source| E["Enqueue/push single cell"]
D --> F["BFS/DFS loop"]
E --> F
F --> G["For each neighbor<br/>check bounds + visited"]
G --> H{Valid & unvisited?}
H -->|Yes| I["Mark visited<br/>Add to queue/stack"]
H -->|No| J[Skip]
I --> F
J --> F
F --> K[Done]
3. Topological Sort — Kahn's BFS
flowchart TD
A["Build adjacency list + in-degree map"] --> B["Enqueue all nodes<br/>with in-degree = 0"]
B --> C{Queue empty?}
C -->|No| D["Dequeue node u<br/>Append to result"]
D --> E["For each neighbor v<br/>of u"]
E --> F["in-degree[v] -= 1"]
F --> G{"in-degree[v] == 0?"}
G -->|Yes| H[Enqueue v]
G -->|No| I[Continue]
H --> C
I --> C
C -->|Yes| J{len result == n?}
J -->|Yes| K["Valid order / no cycle"]
J -->|No| L[Cycle detected]
4. Union-Find Operations
flowchart TD
A["Union-Find Init: parent[i]=i, rank[i]=0"] --> B[Operation?]
B -->|find x| C{"parent[x] == x?"}
C -->|Yes| D[Return x]
C -->|No| E["Path compression: parent[x] = find(parent[x])"]
E --> D
B -->|union x,y| F["rx = find x<br/>ry = find y"]
F --> G{rx == ry?}
G -->|Yes| H["Already connected<br/>return False"]
G -->|No| I{"rank[rx] vs rank[ry]"}
I -->|rx higher| J["parent[ry] = rx"]
I -->|ry higher| K["parent[rx] = ry"]
I -->|equal| L["parent[ry] = rx, rank[rx] += 1"]
J --> M[Return True]
K --> M
L --> M
5. Multi-Source BFS Pattern
flowchart TD
A[Identify ALL source nodes] --> B["Add all sources to queue<br/>Mark all as visited/distance=0"]
B --> C[BFS level by level]
C --> D[Process current level]
D --> E[Expand to neighbors]
E --> F{Unvisited neighbor?}
F -->|Yes| G["dist[neighbor] = dist[cur]+1, Enqueue"]
F -->|No| H[Skip]
G --> C
H --> C
C --> I[All cells processed]
style A fill:#f96,color:#000
style B fill:#f96,color:#000
Complexity Summary
| Problem | Time | Space | Pattern |
|---|---|---|---|
| island_perimeter | O(m×n) | O(1) | Grid traversal |
| verifying_alien_dictionary | O(n·k) | O(1) | Order mapping |
| find_the_town_judge | O(V+E) | O(V) | In/out degree |
| number_of_islands | O(m×n) | O(m×n) | Grid DFS/BFS |
| max_area_of_island | O(m×n) | O(m×n) | Grid DFS |
| clone_graph | O(V+E) | O(V) | DFS + hashmap |
| walls_and_gates | O(m×n) | O(m×n) | Multi-source BFS |
| rotting_oranges | O(m×n) | O(m×n) | Multi-source BFS |
| pacific_atlantic_water_flow | O(m×n) | O(m×n) | Multi-source BFS |
| surrounded_regions | O(m×n) | O(m×n) | DFS from border |
| open_the_lock | O(10⁴) | O(10⁴) | BFS shortest path |
| course_schedule | O(V+E) | O(V+E) | Topological sort / cycle detection |
| course_schedule_ii | O(V+E) | O(V+E) | Topological sort (Kahn's) |
| graph_valid_tree | O(V+E) | O(V) | Union-Find / DFS |
| course_schedule_iv | O(V+E) | O(V²) | Topological sort + reachability |
| number_of_connected_components | O(V·α(V)) | O(V) | Union-Find |
| redundant_connection | O(V·α(V)) | O(V) | Union-Find |
| accounts_merge | O(n·k·α(n)) | O(n·k) | Union-Find |
| evaluate_division | O(Q·(V+E)) | O(V+E) | Weighted DFS/BFS |
| minimum_height_trees | O(V+E) | O(V+E) | Topological sort (leaf trimming) |
| word_ladder | O(M²·N) | O(M²·N) | BFS shortest path |
| number_of_provinces | O(V²·α(V)) | O(V) | Union-Find / DFS |
| flood_fill | O(m×n) | O(m×n) | Grid DFS |
| bipartite_graph | O(V+E) | O(V) | BFS/DFS coloring |
| number_of_enclaves | O(m×n) | O(m×n) | Grid DFS from border |
| distance_of_nearest_cell | O(m×n) | O(m×n) | Multi-source BFS |
| word_ladder_ii | O(M²·N) | O(M²·N) | BFS + backtrack |
| find_eventual_safe_states | O(V+E) | O(V+E) | Topological sort / reverse DFS |
Study Order
- Warm-up — 463, 953, 997 (no traversal needed)
- Grid DFS — 200, 695, 130 (flood fill pattern)
- Grid BFS / Multi-source — 286, 994, 417 (multi-source BFS)
- General graph BFS/DFS — 133, 752, 127 (adjacency list)
- Topological Sort — 207, 210, 1462, 310 (Kahn's algorithm)
- Union-Find — 261, 323, 684, 721 (union-find template)
- Weighted/Advanced — 399 (weighted BFS/DFS)
Graphs — Pattern Notes
Core Intuition
Graphs are about connectivity and traversal. The key questions: Is it a grid (implicit graph)? Do edges have direction? Are there cycles? Do you need shortest path or just reachability?
- BFS = shortest path in unweighted graphs, level-by-level exploration.
- DFS = connectivity, cycle detection, topological sort.
- Union-Find = dynamic connectivity, cycle detection in undirected graphs.
- Topological Sort = ordering with dependencies (DAG only).
- Multi-source BFS = start from multiple nodes simultaneously (e.g., rotting oranges).
Decision Flowchart
flowchart TD
A[Graph Problem] --> B{"Grid / implicit graph?"}
B -- Yes --> C{"Shortest path / min steps?"}
C -- Yes --> D[BFS from source]
C -- No --> E["DFS / flood fill"]
B -- No --> F{Directed graph?}
F -- Yes --> G{Cycle detection or ordering?}
G -- Yes --> H["Topological Sort<br/>Kahn's BFS or DFS"]
G -- No --> I[DFS with visited set]
F -- No --> J{Dynamic connectivity?}
J -- Yes --> K[Union-Find]
J -- No --> L{Multiple sources?}
L -- Yes --> M[Multi-source BFS]
L -- No --> N["BFS / DFS from single source"]
Per-Problem Notes
1. Island Perimeter — LC 463 | Easy
- Key insight: For each land cell, contribute 4 edges. Subtract 2 for each shared edge with a neighbor land cell.
- Tricky: No BFS/DFS needed — pure math on the grid.
- Common mistakes: Trying to use DFS when a simple O(n·m) scan works.
- Time: O(n·m) | Space: O(1)
2. Verifying Alien Dictionary — LC 953 | Easy
- Key insight: Build order map from alien alphabet. For each adjacent word pair, find first differing char and check order.
- Tricky: If word1 is a prefix of word2 but longer → invalid (e.g., "apple" before "app").
- Common mistakes: Not handling the prefix case.
- Time: O(n·m) | Space: O(1) (26 chars)
3. Find the Town Judge — LC 997 | Easy
- Key insight: Judge is trusted by everyone (in-degree = n-1) and trusts nobody (out-degree = 0). Track both with arrays.
- Tricky: A person can't be the judge if they trust anyone.
- Common mistakes: Only checking in-degree, forgetting out-degree check.
- Time: O(n + e) | Space: O(n)
4. Number of Islands — LC 200 | Medium
- Key insight: DFS/BFS flood fill. Mark visited cells as '0' (or use visited set). Count connected components.
- Tricky: Modify grid in-place vs. using a visited set — in-place is O(1) extra space.
- Common mistakes: Not marking cells visited before adding to queue (BFS) → duplicates.
- Time: O(n·m) | Space: O(n·m)
5. Max Area of Island — LC 695 | Medium
- Key insight: Same flood fill as #200, but return size of each component and track max.
- Tricky: Return count from DFS recursion.
- Common mistakes: Forgetting to mark visited before recursing → infinite loop.
- Time: O(n·m) | Space: O(n·m)
6. Clone Graph — LC 133 | Medium
- Key insight: DFS/BFS with a hashmap
{original: clone}. If node already cloned, return clone. - Tricky: The hashmap serves as both visited set and clone registry.
- Common mistakes: Not using the hashmap as visited → infinite loop on cycles.
- Time: O(n + e) | Space: O(n)
7. Walls and Gates — LC 286 | Medium
- Key insight: Multi-source BFS from all gates (0s) simultaneously. Fill INF cells with distance.
- Tricky: Start BFS from all gates at once — don't BFS from each gate separately (O(n·m·g)).
- Common mistakes: BFS from each gate individually → TLE. Not skipping walls (-1).
- Time: O(n·m) | Space: O(n·m)
8. Rotting Oranges — LC 994 | Medium
- Key insight: Multi-source BFS from all rotten oranges. Count fresh oranges; decrement as they rot.
- Tricky: If fresh > 0 after BFS, return -1 (unreachable fresh oranges).
- Common mistakes: Not counting initial fresh oranges. Returning time when queue is empty instead of when fresh == 0.
- Time: O(n·m) | Space: O(n·m)
9. Pacific Atlantic Water Flow — LC 417 | Medium
- Key insight: Reverse thinking — BFS/DFS from ocean borders inward. Find cells reachable from both oceans.
- Tricky: Going "uphill" from ocean (water flows downhill, so reverse: mark cells that can reach ocean).
- Common mistakes: Forward DFS from each cell → O(n²·m²). Must reverse the direction.
- Time: O(n·m) | Space: O(n·m)
10. Surrounded Regions — LC 130 | Medium
- Key insight: Mark 'O's connected to border as safe (DFS from borders). Flip remaining 'O' to 'X'.
- Tricky: Only 'O's NOT connected to border get flipped.
- Common mistakes: Flipping all 'O's. Not doing the border-connected DFS first.
- Time: O(n·m) | Space: O(n·m)
11. Open the Lock — LC 752 | Medium
- Key insight: BFS on state space. Each state is a 4-digit string. 8 neighbors per state (each wheel ±1).
- Tricky: Start state "0000" might be in deadends. Handle this upfront.
- Common mistakes: Not adding initial state to visited before BFS. Forgetting wrap-around (9→0, 0→9).
- Time: O(10⁴) | Space: O(10⁴)
12. Course Schedule — LC 207 | Medium
- Key insight: Detect cycle in directed graph. DFS with 3 states: unvisited(0), visiting(1), visited(2). Cycle if we hit a "visiting" node.
- Tricky: Must distinguish "currently in stack" (visiting) from "fully processed" (visited).
- Common mistakes: Using only a boolean visited — can't detect back edges properly.
- Time: O(V + E) | Space: O(V + E)
13. Course Schedule II — LC 210 | Medium
- Key insight: Topological sort. DFS postorder (add to result after all descendants processed) or Kahn's BFS (in-degree).
- Tricky: If cycle exists, return []. Kahn's: if result size < numCourses → cycle.
- Common mistakes: Not reversing DFS postorder result. Not detecting cycles.
- Time: O(V + E) | Space: O(V + E)
14. Graph Valid Tree — LC 261 | Medium
- Key insight: Valid tree = connected + no cycles = exactly n-1 edges + all nodes connected.
- Tricky: Check edge count first: if edges != n-1, return False immediately.
- Common mistakes: Not checking edge count. Using Union-Find but forgetting connectivity check.
- Time: O(n + e) | Space: O(n)
15. Course Schedule IV — LC 1462 | Medium
- Key insight: Transitive closure. BFS/DFS from each node, or Floyd-Warshall for dense graphs. Store reachability matrix.
- Tricky: Precompute all reachability, then answer queries in O(1).
- Common mistakes: Running BFS per query → TLE.
- Time: O(V·(V+E)) | Space: O(V²)
16. Number of Connected Components — LC 323 | Medium
- Key insight: Union-Find or DFS. Count components = n - number of successful unions.
- Tricky: Union-Find with path compression + union by rank is cleanest.
- Common mistakes: Forgetting to initialize each node as its own parent.
- Time: O(n + e · α(n)) | Space: O(n)
17. Redundant Connection — LC 684 | Medium
- Key insight: Union-Find. Process edges in order; the first edge that connects two already-connected nodes is redundant.
- Tricky: Return the last such edge if multiple exist (problem guarantees one answer).
- Common mistakes: Using DFS cycle detection — works but Union-Find is cleaner and O(α(n)) per operation.
- Time: O(n · α(n)) | Space: O(n)
18. Accounts Merge — LC 721 | Medium
- Key insight: Union-Find on emails. Union all emails in same account. Group by root, sort, prepend name.
- Tricky: Map email → account name for the root email. Sort emails within each group.
- Common mistakes: Not sorting the final email lists. Forgetting to map root email to account name.
- Time: O(n·m · α(n·m)) | Space: O(n·m)
19. Evaluate Division — LC 399 | Medium
- Key insight: Build weighted directed graph. BFS/DFS to find path product between query nodes.
- Tricky: Add both directions: a/b = x means add edge a→b (x) and b→a (1/x).
- Common mistakes: Not adding reverse edges. Not handling queries where nodes don't exist in graph.
- Time: O((V+E) · Q) | Space: O(V + E)
20. Minimum Height Trees — LC 310 | Medium
- Key insight: Topological sort from leaves inward. Iteratively remove leaf nodes. Last 1-2 nodes are roots.
- Tricky: Answer is always 1 or 2 nodes. Stop when ≤ 2 nodes remain.
- Common mistakes: BFS from all nodes to find min height — O(n²). Must use leaf-trimming approach.
- Time: O(n) | Space: O(n)
21. Word Ladder — LC 127 | Hard
- Key insight: BFS on word state space. Each word's neighbors = all words differing by 1 char. Use word set for O(1) lookup.
- Tricky: Generate neighbors by replacing each char with a-z (26·L neighbors per word). Remove from set when visited.
- Common mistakes: Using edit distance between all pairs → O(n²·L). Not removing visited words from set → TLE.
- Time: O(n·L·26) | Space: O(n·L)
Edge Cases to Watch For
- Disconnected graph: Always check if all nodes are visited after traversal.
- Self-loops: Handle in cycle detection and Union-Find.
- Empty graph / single node: Return appropriate base case.
- Grid boundaries: Always check
0 <= r < rows and 0 <= c < colsbefore accessing. - Already visited in BFS: Add to visited when enqueuing, not when dequeuing — prevents duplicates.
- Directed vs undirected: Cycle detection differs. Undirected: parent tracking. Directed: 3-color DFS.
- Topological sort on cyclic graph: Must detect cycle and return empty/invalid.
- Multi-source BFS initial state: Add ALL sources to queue and visited set before starting BFS.
| # | Problem | LC # | Difficulty | Key Technique |
|---|---|---|---|---|
| 1 | Island Perimeter | 463 | Easy | Grid traversal |
| 2 | Verifying Alien Dictionary | 953 | Easy | Order mapping |
| 3 | Find Town Judge | 997 | Easy | In/out degree |
| 4 | Number of Islands | 200 | Medium | DFS/BFS/Union-Find |
| 5 | Max Area of Island | 695 | Medium | DFS |
| 6 | Clone Graph | 133 | Medium | DFS + hashmap |
| 7 | Walls and Gates | 286 | Medium | Multi-source BFS |
| 8 | Rotting Oranges | 994 | Medium | Multi-source BFS |
| 9 | Pacific Atlantic Water Flow | 417 | Medium | Multi-source BFS |
| 10 | Surrounded Regions | 130 | Medium | DFS from border |
| 11 | Open the Lock | 752 | Medium | BFS shortest path |
| 12 | Course Schedule | 207 | Medium | Topological sort / cycle detection |
| 13 | Course Schedule II | 210 | Medium | Topological sort (Kahn's) |
| 14 | Graph Valid Tree | 261 | Medium | Union-Find / DFS |
| 15 | Course Schedule IV | 1462 | Medium | Topological sort + reachability |
| 16 | Number of Connected Components | 323 | Medium | Union-Find |
| 17 | Redundant Connection | 684 | Medium | Union-Find |
| 18 | Accounts Merge | 721 | Medium | Union-Find |
| 19 | Evaluate Division | 399 | Medium | Weighted DFS/BFS |
| 20 | Minimum Height Trees | 310 | Medium | Topological sort (leaf trimming) |
| 21 | Word Ladder | 127 | Hard | BFS shortest path |
| 22 | Number of Provinces | 547 | Medium | Union-Find / DFS |
| 23 | Flood Fill | 733 | Easy | Grid DFS |
| 24 | Bipartite Graph | 785 | Medium | BFS/DFS coloring |
| 25 | Number of Enclaves | 1020 | Medium | Grid DFS from border |
| 26 | Distance of Nearest Cell (01 Matrix) | 542 | Medium | Multi-source BFS |
| 27 | Word Ladder II | 126 | Hard | BFS + backtrack |
| 28 | Find Eventual Safe States | 802 | Medium | Topological sort / reverse DFS |
Accounts Merge ▼ expand
from typing import List
from collections import defaultdict
class Solution:
"""
## 721. Accounts Merge
Given a list of `accounts` where each element `accounts[i]` is a list of strings,
where `accounts[i][0]` is a name and the rest are emails belonging to that account.
Merge accounts that share at least one common email. Return the merged accounts in
any order. Within each account, return emails in sorted order, with the name first.
### Examples
```text
Input:
accounts = [
["John","johnsmith@mail.com","john_newyork@mail.com"],
["John","johnsmith@mail.com","john00@mail.com"],
["Mary","mary@mail.com"],
["John","johnnybravo@mail.com"]
]
Output:
[
["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],
["Mary","mary@mail.com"],
["John","johnnybravo@mail.com"]
]
Input:
accounts = [["John","j1@","j2@"],["John","j1@","j3@"]]
Output:
[["John","j1@","j2@","j3@"]]
```
### Constraints
- `1 <= accounts.length <= 1000`
- `2 <= accounts[i].length <= 10`
- `1 <= accounts[i][j].length <= 30`
- `accounts[i][0]` consists of English letters.
- `accounts[i][j]` (for `j > 0`) is a valid email.
"""
def accountsMerge_dfs(self, accounts: List[List[str]]) -> List[List[str]]:
# O(n*k*log(n*k)) time, O(n*k) space
email_to_accounts = defaultdict(list)
for i, account in enumerate(accounts):
for email in account[1:]:
email_to_accounts[email].append(i)
visited_accounts = set()
result = []
def dfs(idx: int, emails: set) -> None:
if idx in visited_accounts:
return
visited_accounts.add(idx)
for email in accounts[idx][1:]:
emails.add(email)
for neighbor in email_to_accounts[email]:
dfs(neighbor, emails)
for i in range(len(accounts)):
if i not in visited_accounts:
emails: set = set()
dfs(i, emails)
result.append([accounts[i][0]] + sorted(emails))
return result
def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
# Union-Find: union all emails within the same account
# Group emails by their root representative; attach account name
# O(n*k*alpha(n*k)) time, O(n*k) space — Union-Find
parent: dict = {}
rank: dict = {}
def find(x: str) -> str:
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x: str, y: str) -> None:
px, py = find(x), find(y)
if px == py:
return
if rank.get(px, 0) < rank.get(py, 0):
px, py = py, px
parent[py] = px
if rank.get(px, 0) == rank.get(py, 0):
rank[px] = rank.get(px, 0) + 1
email_to_name: dict = {}
for account in accounts:
name = account[0]
for email in account[1:]:
if email not in parent:
parent[email] = email
email_to_name[email] = name
union(account[1], email)
groups: dict = defaultdict(list)
for email in parent:
groups[find(email)].append(email)
return [[email_to_name[root]] + sorted(emails) for root, emails in groups.items()]
if __name__ == '__main__':
s = Solution()
accounts1 = [
["John", "johnsmith@mail.com", "john_newyork@mail.com"],
["John", "johnsmith@mail.com", "john00@mail.com"],
["Mary", "mary@mail.com"],
["John", "johnnybravo@mail.com"],
]
res = s.accountsMerge(accounts1)
assert len(res) == 3
accounts2 = [["John", "j1@", "j2@"], ["John", "j1@", "j3@"]]
res2 = s.accountsMerge(accounts2)
assert len(res2) == 1 and sorted(res2[0][1:]) == ["j1@", "j2@", "j3@"]
res3 = s.accountsMerge_dfs(accounts2)
assert len(res3) == 1 and sorted(res3[0][1:]) == ["j1@", "j2@", "j3@"]
print("All tests passed.") Bipartite Graph ▼ expand
"""
# LC 785: Is Graph Bipartite?
A graph is bipartite if its nodes can be split into two groups such that
every edge connects nodes from different groups (i.e., it is 2-colorable and
contains no odd-length cycles). Return whether the given adjacency-list graph
is bipartite.
## Examples
```text
Input: graph = [[1,3],[0,2],[1,3],[0,2]]
Output: True
Input: graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
Output: False
```
## Constraints
- Disconnected components must each be checked independently.
- A self-loop makes a graph non-bipartite.
- An empty graph or a single node is bipartite.
"""
from typing import List
from collections import deque
class Solution:
# Approach 1: BFS coloring - try to 2-color the graph
def isBipartite(self, graph: List[List[int]]) -> bool:
n = len(graph)
color = [-1] * n # -1 = unvisited, 0 and 1 are the two colors
for start in range(n): # handle disconnected components
if color[start] != -1:
continue
queue = deque([start])
color[start] = 0
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if color[neighbor] == -1:
color[neighbor] = 1 - color[node] # assign opposite color
queue.append(neighbor)
elif color[neighbor] == color[node]:
return False # same color on both ends = odd cycle
return True
# Approach 2: DFS coloring
def isBipartite_dfs(self, graph: List[List[int]]) -> bool:
n = len(graph)
color = [-1] * n
def dfs(node, c):
color[node] = c
for neighbor in graph[node]:
if color[neighbor] == -1:
if not dfs(neighbor, 1 - c):
return False
elif color[neighbor] == c:
return False # conflict
return True
for i in range(n):
if color[i] == -1:
if not dfs(i, 0):
return False
return True
if __name__ == "__main__":
s = Solution()
assert s.isBipartite([[1,3],[0,2],[1,3],[0,2]]) is True
assert s.isBipartite([[1,2,3],[0,2],[0,1,3],[0,2]]) is False
assert s.isBipartite_dfs([[1,3],[0,2],[1,3],[0,2]]) is True
assert s.isBipartite_dfs([[1,2,3],[0,2],[0,1,3],[0,2]]) is False
print("All tests passed.") Clone Graph ▼ expand
"""
# 133. Clone Graph
## Problem
Given a reference of a node in a connected undirected graph, return a deep copy (clone)
of the graph. Each node in the graph contains a value (`int`) and a list of its neighbors.
```text
class Node {
public int val;
public List<Node> neighbors;
}
```
## Examples
```text
Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
Explanation: 4 nodes, node 1 connects to 2 and 4, node 2 connects to 1 and 3, etc.
```
```text
Input: adjList = [[]]
Output: [[]]
Explanation: Single node with no neighbors.
```
```text
Input: adjList = []
Output: []
Explanation: Empty graph.
```
## Constraints
- The number of nodes in the graph is in the range `[0, 100]`
- `1 <= Node.val <= 100`
- `Node.val` is unique for each node
- There are no repeated edges and no self-loops
- The graph is connected (all nodes can be visited starting from the given node)
"""
from typing import Optional
from collections import deque
class Node:
def __init__(self, val=0, neighbors=None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
class Solution:
def cloneGraph_dfs(self, node: Optional['Node']) -> Optional['Node']:
# O(V+E) time, O(V) space — DFS with visited map
if not node:
return None
visited = {}
def dfs(n):
if n in visited:
return visited[n]
clone = Node(n.val)
visited[n] = clone
for nb in n.neighbors:
clone.neighbors.append(dfs(nb))
return clone
return dfs(node)
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
# BFS/DFS with a visited map: old_node -> new_node clone
# For each node, clone it if not seen, then clone all its neighbors
# O(V+E) time, O(V) space — BFS with visited map
if not node:
return None
visited = {node: Node(node.val)}
q = deque([node])
while q:
curr = q.popleft()
for nb in curr.neighbors:
if nb not in visited:
visited[nb] = Node(nb.val)
q.append(nb)
visited[curr].neighbors.append(visited[nb])
return visited[node]
def build_graph(adj: list) -> Optional[Node]:
if not adj:
return None
nodes = [Node(i+1) for i in range(len(adj))]
for i, neighbors in enumerate(adj):
nodes[i].neighbors = [nodes[j-1] for j in neighbors]
return nodes[0]
def graph_to_adj(node: Optional[Node]) -> list:
if not node:
return []
visited, result = {}, {}
q = deque([node])
visited[node.val] = node
while q:
curr = q.popleft()
result[curr.val] = sorted(nb.val for nb in curr.neighbors)
for nb in curr.neighbors:
if nb.val not in visited:
visited[nb.val] = nb
q.append(nb)
return [result[i] for i in sorted(result)]
if __name__ == '__main__':
s = Solution()
adj = [[2,4],[1,3],[2,4],[1,3]]
g = build_graph(adj)
assert graph_to_adj(s.cloneGraph_dfs(g)) == [[2,4],[1,3],[2,4],[1,3]]
assert graph_to_adj(s.cloneGraph(build_graph(adj))) == [[2,4],[1,3],[2,4],[1,3]]
assert s.cloneGraph(None) is None
print("All tests passed.") Course Schedule ▼ expand
"""
# 207. Course Schedule
## Problem
There are a total of `numCourses` courses you have to take, labeled from `0` to
`numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [a, b]`
indicates that you must take course `b` first if you want to take course `a`.
Return `true` if you can finish all courses. Otherwise, return `false`.
## Examples
```text
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: Take course 0 first, then course 1.
```
```text
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: Courses 0 and 1 are mutually dependent — impossible.
```
```text
Input: numCourses = 5, prerequisites = [[1,0],[2,1],[3,2],[4,3]]
Output: true
```
## Constraints
- `1 <= numCourses <= 2000`
- `0 <= prerequisites.length <= 5000`
- `prerequisites[i].length == 2`
- `0 <= a, b < numCourses`
- All the pairs `prerequisites[i]` are unique
"""
from typing import List
from collections import deque, defaultdict
class Solution:
def canFinish_dfs(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# O(V+E) time, O(V+E) space — DFS cycle detection with 3-color marking
graph = defaultdict(list)
for a, b in prerequisites:
graph[b].append(a)
# 0=unvisited, 1=visiting, 2=done
state = [0] * numCourses
def dfs(node):
if state[node] == 1:
return False # back edge → cycle
if state[node] == 2:
return True
state[node] = 1
for nb in graph[node]:
if not dfs(nb):
return False
state[node] = 2
return True
return all(dfs(i) for i in range(numCourses))
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Build adjacency list; detect cycle using DFS with 3-state coloring
# States: 0=unvisited, 1=in-progress (cycle if revisited), 2=done
# O(V+E) time, O(V+E) space — BFS topological sort (Kahn's algorithm)
graph = defaultdict(list)
indegree = [0] * numCourses
for a, b in prerequisites:
graph[b].append(a)
indegree[a] += 1
q = deque(i for i in range(numCourses) if indegree[i] == 0)
processed = 0
while q:
node = q.popleft()
processed += 1
for nb in graph[node]:
indegree[nb] -= 1
if indegree[nb] == 0:
q.append(nb)
return processed == numCourses
if __name__ == '__main__':
s = Solution()
assert s.canFinish_dfs(2, [[1,0]]) == True
assert s.canFinish_dfs(2, [[1,0],[0,1]]) == False
assert s.canFinish_dfs(5, [[1,0],[2,1],[3,2],[4,3]]) == True
assert s.canFinish(2, [[1,0]]) == True
assert s.canFinish(2, [[1,0],[0,1]]) == False
assert s.canFinish(1, []) == True
print("All tests passed.") Course Schedule Ii ▼ expand
from typing import List
from collections import deque
class Solution:
"""
## 210. Course Schedule II
There are `numCourses` courses labeled `0` to `numCourses - 1`. You are given
an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates you must
take course `bi` first if you want to take course `ai`.
Return the ordering of courses you should take to finish all courses. If there
are many valid answers, return any of them. If it is impossible to finish all
courses, return an empty array.
### Examples
```text
Input: numCourses = 2, prerequisites = [[1,0]]
Output: [0, 1]
Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: [0,2,1,3]
Input: numCourses = 1, prerequisites = []
Output: [0]
```
### Constraints
- `1 <= numCourses <= 2000`
- `0 <= prerequisites.length <= numCourses * (numCourses - 1)`
- `prerequisites[i].length == 2`
- `0 <= ai, bi < numCourses`
- `ai != bi`
- All pairs `[ai, bi]` are distinct.
"""
def findOrder_dfs(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# O(V+E) time, O(V+E) space
graph = [[] for _ in range(numCourses)]
for a, b in prerequisites:
graph[b].append(a)
# 0=unvisited, 1=visiting, 2=visited
state = [0] * numCourses
order = []
def dfs(node: int) -> bool:
if state[node] == 1:
return False # cycle
if state[node] == 2:
return True
state[node] = 1
for nei in graph[node]:
if not dfs(nei):
return False
state[node] = 2
order.append(node)
return True
for i in range(numCourses):
if not dfs(i):
return []
return order[::-1]
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# Topological sort via DFS post-order; cycle detection aborts
# Reverse post-order gives valid course order
# O(V+E) time, O(V+E) space — Kahn's BFS
graph = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for a, b in prerequisites:
graph[b].append(a)
indegree[a] += 1
queue = deque(i for i in range(numCourses) if indegree[i] == 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) == numCourses else []
if __name__ == '__main__':
s = Solution()
assert s.findOrder(2, [[1, 0]]) == [0, 1]
result = s.findOrder(4, [[1, 0], [2, 0], [3, 1], [3, 2]])
assert result[0] == 0 and result[-1] == 3
assert s.findOrder(2, [[1, 0], [0, 1]]) == []
assert s.findOrder(1, []) == [0]
assert s.findOrder_dfs(2, [[1, 0]]) == [0, 1]
assert s.findOrder_dfs(2, [[1, 0], [0, 1]]) == []
print("All tests passed.") Course Schedule Iv ▼ expand
from typing import List
from collections import deque
class Solution:
"""
## 1462. Course Schedule IV
There are `numCourses` courses labeled `0` to `numCourses - 1`. You are given
an array `prerequisites` where `prerequisites[i] = [ai, bi]` means course `ai`
must be taken before course `bi`.
Given a list of `queries` where `queries[j] = [uj, vj]`, answer whether course
`uj` is a prerequisite of course `vj` (directly or indirectly).
### Examples
```text
Input: numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
Output: [false, true]
Input: numCourses = 2, prerequisites = [], queries = [[1,0],[0,1]]
Output: [false, false]
Input: numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
Output: [true, true]
```
### Constraints
- `2 <= numCourses <= 100`
- `0 <= prerequisites.length <= (numCourses * (numCourses - 1) / 2)`
- `prerequisites[i].length == 2`
- `0 <= ai, bi < numCourses`
- `ai != bi`
- All prerequisite pairs are distinct.
- `1 <= queries.length <= 10^4`
"""
def checkIfPrerequisite_bfs(self, numCourses: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]:
# O(V*(V+E)) time, O(V^2) space
graph = [[] for _ in range(numCourses)]
for a, b in prerequisites:
graph[a].append(b)
def can_reach(src: int, dst: int) -> bool:
visited = set()
queue = deque([src])
while queue:
node = queue.popleft()
if node == dst:
return True
for nei in graph[node]:
if nei not in visited:
visited.add(nei)
queue.append(nei)
return False
return [can_reach(u, v) for u, v in queries]
def checkIfPrerequisite(self, numCourses: int, prerequisites: List[List[int]], queries: List[List[int]]) -> List[bool]:
# Build reachability matrix via DFS/BFS or Floyd-Warshall
# reachable[u][v] = True if u can reach v through prerequisites
# O(V^3) time, O(V^2) space — Floyd-Warshall transitive closure
reach = [[False] * numCourses for _ in range(numCourses)]
for a, b in prerequisites:
reach[a][b] = True
for k in range(numCourses):
for i in range(numCourses):
for j in range(numCourses):
if reach[i][k] and reach[k][j]:
reach[i][j] = True
return [reach[u][v] for u, v in queries]
if __name__ == '__main__':
s = Solution()
assert s.checkIfPrerequisite(2, [[1, 0]], [[0, 1], [1, 0]]) == [False, True]
assert s.checkIfPrerequisite(2, [], [[1, 0], [0, 1]]) == [False, False]
assert s.checkIfPrerequisite(3, [[1, 2], [1, 0], [2, 0]], [[1, 0], [1, 2]]) == [True, True]
assert s.checkIfPrerequisite_bfs(2, [[1, 0]], [[0, 1], [1, 0]]) == [False, True]
assert s.checkIfPrerequisite_bfs(3, [[1, 2], [1, 0], [2, 0]], [[1, 0], [1, 2]]) == [True, True]
print("All tests passed.") Distance Of Nearest Cell ▼ expand
"""
# LC 542: 01 Matrix (Distance of Nearest 0)
For each cell in a binary matrix, find the distance to the nearest `0`, where
distance is the number of 4-directional steps.
## Examples
```text
Input: mat = [[0,0,0],[0,1,0],[1,1,1]]
Output: [[0,0,0],[0,1,0],[1,2,1]]
Input: mat = [[0,0,0],[0,1,0],[0,0,0]]
Output: [[0,0,0],[0,1,0],[0,0,0]]
```
## Constraints
- A cell that is already `0` has distance 0.
- Use multi-source BFS from all `0` cells simultaneously.
- At least one `0` is present in the matrix.
"""
from typing import List
from collections import deque
class Solution:
# Multi-source BFS: start from ALL 0s simultaneously, expand outward
def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
rows, cols = len(mat), len(mat[0])
dist = [[float('inf')] * cols for _ in range(rows)]
queue = deque()
# initialize BFS with all 0-cells at distance 0
for r in range(rows):
for c in range(cols):
if mat[r][c] == 0:
dist[r][c] = 0
queue.append((r, c))
# BFS expands layer by layer - first time we reach a 1-cell is shortest distance
while queue:
r, c = queue.popleft()
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
if dist[nr][nc] > dist[r][c] + 1:
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))
return dist
if __name__ == "__main__":
s = Solution()
assert s.updateMatrix([[0,0,0],[0,1,0],[1,1,1]]) == [[0,0,0],[0,1,0],[1,2,1]]
assert s.updateMatrix([[0,0,0],[0,1,0],[0,0,0]]) == [[0,0,0],[0,1,0],[0,0,0]]
print("All tests passed.") Evaluate Division ▼ expand
from typing import List
from collections import defaultdict, deque
class Solution:
"""
## 399. Evaluate Division
You are given an array of variable pairs `equations` and an array of real numbers
`values`, where `equations[i] = [Ai, Bi]` and `values[i]` represent the equation
`Ai / Bi = values[i]`.
Given some `queries`, where `queries[j] = [Cj, Dj]`, return the answers to all
queries. If the answer does not exist, return `-1.0`.
### Examples
```text
Input:
equations = [["a","b"],["b","c"]], values = [2.0,3.0]
queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]]
Output: [6.0, 0.5, -1.0, 1.0, -1.0]
Input:
equations = [["a","b"],["b","c"]], values = [2.0,3.0]
queries = [["a","c"]]
Output: [6.0]
```
### Constraints
- `1 <= equations.length <= 20`
- `equations[i].length == 2`
- `1 <= Ai.length, Bi.length <= 5`
- `values[i] > 0.0`
- `1 <= queries.length <= 20`
"""
def calcEquation_bfs(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
# O(Q*(V+E)) time, O(V+E) space
graph: dict = defaultdict(dict)
for (a, b), val in zip(equations, values):
graph[a][b] = val
graph[b][a] = 1.0 / val
def bfs(src: str, dst: str) -> float:
if src not in graph or dst not in graph:
return -1.0
if src == dst:
return 1.0
visited = set()
queue = deque([(src, 1.0)])
visited.add(src)
while queue:
node, product = queue.popleft()
if node == dst:
return product
for nei, weight in graph[node].items():
if nei not in visited:
visited.add(nei)
queue.append((nei, product * weight))
return -1.0
return [bfs(c, d) for c, d in queries]
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
# Build weighted graph: a/b=k means edge a->b with weight k, b->a with 1/k
# BFS/DFS for each query to find the product of weights along the path
# O(E*alpha(V) + Q) time, O(V) space — Union-Find with weights
parent: dict = {}
weight: dict = {} # weight[x] = x / find(x)
def find(x: str):
if x not in parent:
parent[x] = x
weight[x] = 1.0
if parent[x] != x:
root, w = find(parent[x])
parent[x] = root
weight[x] *= w
return parent[x], weight[x]
def union(x: str, y: str, val: float) -> None:
rx, wx = find(x)
ry, wy = find(y)
if rx != ry:
parent[rx] = ry
weight[rx] = val * wy / wx
for (a, b), val in zip(equations, values):
union(a, b, val)
results = []
for c, d in queries:
if c not in parent or d not in parent:
results.append(-1.0)
else:
rc, wc = find(c)
rd, wd = find(d)
results.append(wc / wd if rc == rd else -1.0)
return results
if __name__ == '__main__':
s = Solution()
eq = [["a", "b"], ["b", "c"]]
vals = [2.0, 3.0]
queries = [["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"]]
expected = [6.0, 0.5, -1.0, 1.0, -1.0]
res = s.calcEquation_bfs(eq, vals, queries)
assert res == expected, res
res2 = s.calcEquation(eq, vals, [["a", "c"]])
assert abs(res2[0] - 6.0) < 1e-9
print("All tests passed.") Find Eventual Safe States ▼ expand
"""
# LC 802: Find Eventual Safe States
Return, in ascending order, all nodes of a directed graph from which every
path eventually leads to a terminal node (no cycle is reachable).
## Examples
```text
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2,4,5,6]
Input: graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]]
Output: [4]
```
## Constraints
- A node with no outgoing edges is always safe (terminal).
- If all nodes form one cycle, return `[]`.
- Handle disconnected components.
"""
from typing import List
from collections import deque
class Solution:
# Approach 1: DFS with 3-color marking (0=unvisited, 1=in-stack, 2=safe)
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
n = len(graph)
state = [0] * n # 0=unvisited, 1=visiting(in cycle path), 2=safe
def dfs(node) -> bool:
if state[node] == 1: return False # back edge = cycle
if state[node] == 2: return True # already confirmed safe
state[node] = 1 # mark as being visited
for neighbor in graph[node]:
if not dfs(neighbor):
return False # neighbor leads to cycle
state[node] = 2 # all neighbors safe, mark safe
return True
return [i for i in range(n) if dfs(i)]
# Approach 2: Reverse graph + topological sort (Kahn's)
def eventualSafeNodes_topo(self, graph: List[List[int]]) -> List[int]:
n = len(graph)
rev = [[] for _ in range(n)]
out_degree = [0] * n
for u in range(n):
for v in graph[u]:
rev[v].append(u) # reverse edge
out_degree[u] = len(graph[u])
# nodes with out_degree 0 are terminal (safe)
queue = deque(i for i in range(n) if out_degree[i] == 0)
safe = set()
while queue:
node = queue.popleft()
safe.add(node)
for parent in rev[node]:
out_degree[parent] -= 1
if out_degree[parent] == 0:
queue.append(parent)
return sorted(safe)
if __name__ == "__main__":
s = Solution()
assert s.eventualSafeNodes([[1,2],[2,3],[5],[0],[5],[],[]]) == [2,4,5,6]
assert s.eventualSafeNodes([[1,2,3,4],[1,2],[3,4],[0,4],[]]) == [4]
assert s.eventualSafeNodes_topo([[1,2],[2,3],[5],[0],[5],[],[]]) == [2,4,5,6]
assert s.eventualSafeNodes_topo([[1,2,3,4],[1,2],[3,4],[0,4],[]]) == [4]
print("All tests passed.") Find The Town Judge ▼ expand
"""
# 997. Find the Town Judge
## Problem
In a town, there are `n` people labeled from `1` to `n`. There is a rumor that one of
these people is secretly the town judge. If the town judge exists, then:
1. The town judge trusts nobody.
2. Everybody (except the town judge) trusts the town judge.
3. There is exactly one person that satisfies properties 1 and 2.
Given an array `trust` where `trust[i] = [a, b]` means person `a` trusts person `b`,
return the label of the town judge if the town judge exists, otherwise return `-1`.
## Examples
```text
Input: n = 2, trust = [[1,2]]
Output: 2
```
```text
Input: n = 3, trust = [[1,3],[2,3]]
Output: 3
```
```text
Input: n = 3, trust = [[1,3],[2,3],[3,1]]
Output: -1
```
## Constraints
- `1 <= n <= 1000`
- `0 <= trust.length <= 10^4`
- `trust[i].length == 2`
- All the pairs in `trust` are unique
- `a != b`
"""
from typing import List
class Solution:
def findJudge(self, n: int, trust: List[List[int]]) -> int:
# Judge trusts nobody (out-degree 0) and is trusted by everyone else (in-degree n-1)
# Track trust counts: net_trust[i] = in_degree[i] - out_degree[i]; judge has n-1
# O(n+e) time, O(n) space — net trust score: +1 for being trusted, -1 for trusting
score = [0] * (n + 1)
for a, b in trust:
score[a] -= 1
score[b] += 1
for person in range(1, n + 1):
if score[person] == n - 1:
return person
return -1
if __name__ == '__main__':
s = Solution()
assert s.findJudge(2, [[1,2]]) == 2
assert s.findJudge(3, [[1,3],[2,3]]) == 3
assert s.findJudge(3, [[1,3],[2,3],[3,1]]) == -1
assert s.findJudge(1, []) == 1
print("All tests passed.") Flood Fill ▼ expand
"""
# LC 733: Flood Fill
Starting from pixel `(sr, sc)`, change all 4-directionally connected pixels
that share the same original color to `color`, and return the image.
## Examples
```text
Input: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2
Output: [[2,2,2],[2,2,0],[2,0,1]]
Input: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0
Output: [[0,0,0],[0,0,0]]
```
## Constraints
- If the starting pixel already has the target color, return as-is (no-op).
- A single-pixel image just changes that pixel.
- Only pixels connected to the start with the original color are repainted.
"""
from typing import List
class Solution:
# DFS flood fill - change all connected same-color pixels to new color
def floodFill(self, image: List[List[int]], sr: int, sc: int, color: int) -> List[List[int]]:
original = image[sr][sc]
if original == color:
return image # edge case: already the target color, avoid infinite loop
rows, cols = len(image), len(image[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if image[r][c] != original:
return
image[r][c] = color # paint in-place
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
dfs(sr, sc)
return image
if __name__ == "__main__":
s = Solution()
assert s.floodFill([[1,1,1],[1,1,0],[1,0,1]], 1, 1, 2) == [[2,2,2],[2,2,0],[2,0,1]]
assert s.floodFill([[0,0,0],[0,0,0]], 0, 0, 0) == [[0,0,0],[0,0,0]]
print("All tests passed.") Graph Valid Tree ▼ expand
from typing import List
from collections import deque
class Solution:
"""
## 261. Graph Valid Tree
You have `n` nodes labeled from `0` to `n - 1` and a list of undirected edges.
Write a function to check whether these edges make up a valid tree.
A valid tree must be connected and contain no cycles.
### Examples
```text
Input: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Output: true
Input: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
Output: false
```
### Constraints
- `1 <= n <= 2000`
- `0 <= edges.length <= 5000`
- `edges[i].length == 2`
- `0 <= ai, bi < n`
- `ai != bi`
- No self-loops or repeated edges.
"""
def validTree_bfs(self, n: int, edges: List[List[int]]) -> bool:
# O(V+E) time, O(V+E) space
if len(edges) != n - 1:
return False
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
visited = set()
queue = deque([0])
visited.add(0)
while queue:
node = queue.popleft()
for nei in graph[node]:
if nei not in visited:
visited.add(nei)
queue.append(nei)
return len(visited) == n
def validTree(self, n: int, edges: List[List[int]]) -> bool:
# A valid tree has exactly n-1 edges and is fully connected (no cycles)
# Union-Find: if any edge connects already-connected nodes, it's a cycle
# O(V+E) time, O(V) space — Union-Find
if len(edges) != n - 1:
return False
parent = list(range(n))
rank = [0] * n
def find(x: int) -> int:
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(x: int, y: int) -> bool:
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
return all(union(a, b) for a, b in edges)
if __name__ == '__main__':
s = Solution()
assert s.validTree(5, [[0, 1], [0, 2], [0, 3], [1, 4]]) is True
assert s.validTree(5, [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]) is False
assert s.validTree(1, []) is True
assert s.validTree(4, [[0, 1], [2, 3]]) is False
assert s.validTree_bfs(5, [[0, 1], [0, 2], [0, 3], [1, 4]]) is True
assert s.validTree_bfs(5, [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]) is False
print("All tests passed.") Island Perimeter ▼ expand
"""
# 463. Island Perimeter
## Problem
You are given `row x col` grid representing a map where `grid[i][j] = 1` represents land
and `grid[i][j] = 0` represents water. Grid cells are connected horizontally/vertically.
There is exactly one island (no lakes). Return the perimeter of the island.
## Examples
```text
Input: grid = [[0,1,0,0],
[1,1,1,0],
[0,1,0,0],
[1,1,0,0]]
Output: 16
```
```text
Input: grid = [[1]]
Output: 4
```
```text
Input: grid = [[1,0]]
Output: 4
```
## Constraints
- `row == grid.length`
- `col == grid[i].length`
- `1 <= row, col <= 100`
- `grid[i][j]` is `0` or `1`
- There is exactly one island in `grid`
"""
from typing import List
from collections import deque
class Solution:
def islandPerimeter_counting(self, grid: List[List[int]]) -> int:
# O(m*n) time, O(1) space
rows, cols = len(grid), len(grid[0])
perimeter = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
perimeter += 4
if r > 0 and grid[r-1][c] == 1:
perimeter -= 2
if c > 0 and grid[r][c-1] == 1:
perimeter -= 2
return perimeter
def islandPerimeter(self, grid: List[List[int]]) -> int:
# For each land cell, add 4 sides; subtract 2 for each shared edge with neighbor
# O(m*n) time, O(m*n) space — DFS
rows, cols = len(grid), len(grid[0])
visited = set()
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
return 1
if (r, c) in visited:
return 0
visited.add((r, c))
return dfs(r+1, c) + dfs(r-1, c) + dfs(r, c+1) + dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
return dfs(r, c)
return 0
if __name__ == '__main__':
s = Solution()
assert s.islandPerimeter([[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]) == 16
assert s.islandPerimeter([[1]]) == 4
assert s.islandPerimeter([[1,0]]) == 4
assert s.islandPerimeter_counting([[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]) == 16
print("All tests passed.") Max Area Of Island ▼ expand
"""
# 695. Max Area of Island
## Problem
You are given an `m x n` binary matrix `grid`. An island is a group of `1`s (representing
land) connected 4-directionally (horizontal or vertical). The area of an island is the
number of cells with value `1` in the island. Return the maximum area of an island in
`grid`. If there is no island, return `0`.
## Examples
```text
Input: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
Output: 6
```
```text
Input: grid = [[0,0,0,0,0,0,0,0]]
Output: 0
```
## Constraints
- `m == grid.length`
- `n == grid[i].length`
- `1 <= m, n <= 50`
- `grid[i][j]` is `0` or `1`
"""
from typing import List
from collections import deque
class Solution:
def maxAreaOfIsland_dfs(self, grid: List[List[int]]) -> int:
# O(m*n) time, O(m*n) space — DFS flood fill tracking area
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 0
grid[r][c] = 0
return 1 + dfs(r+1,c) + dfs(r-1,c) + dfs(r,c+1) + dfs(r,c-1)
return max(dfs(r, c) for r in range(rows) for c in range(cols) if grid[r][c] == 1) or 0
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
# DFS flood fill: return size of each island; track global maximum
# O(m*n) time, O(m*n) space — BFS flood fill tracking area
rows, cols = len(grid), len(grid[0])
best = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
grid[r][c] = 0
area, q = 1, deque([(r, c)])
while q:
cr, cc = q.popleft()
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = cr+dr, cc+dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 0
area += 1
q.append((nr, nc))
best = max(best, area)
return best
if __name__ == '__main__':
import copy
s = Solution()
g = [[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
assert s.maxAreaOfIsland_dfs(copy.deepcopy(g)) == 6
assert s.maxAreaOfIsland(copy.deepcopy(g)) == 6
assert s.maxAreaOfIsland([[0,0,0,0,0,0,0,0]]) == 0
print("All tests passed.") Minimum Height Trees ▼ expand
from typing import List
from collections import deque
class Solution:
"""
## 310. Minimum Height Trees
A tree is an undirected graph in which any two vertices are connected by exactly
one path. Given a tree of `n` nodes labeled from `0` to `n - 1`, and an array of
`n - 1` edges, find all the nodes that can be the root of a Minimum Height Tree (MHT).
Return a list of all MHT root labels. You may return the answer in any order.
### Examples
```text
Input: n = 4, edges = [[1,0],[1,2],[1,3]]
Output: [1]
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
Output: [3,4]
Input: n = 1, edges = []
Output: [0]
```
### Constraints
- `1 <= n <= 2 * 10^4`
- `edges.length == n - 1`
- `0 <= ai, bi < n`
- `ai != bi`
- All pairs `(ai, bi)` are distinct.
- The given input is guaranteed to be a tree.
"""
def findMinHeightTrees_brute(self, n: int, edges: List[List[int]]) -> List[int]:
# O(V^2) time, O(V+E) space — BFS from each node
if n == 1:
return [0]
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
def bfs_height(root: int) -> int:
visited = {root}
queue = deque([root])
height = 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)
height += 1
return height
min_h = float('inf')
heights = []
for i in range(n):
h = bfs_height(i)
heights.append(h)
min_h = min(min_h, h)
return [i for i, h in enumerate(heights) if h == min_h]
def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
# Iteratively remove leaf nodes (degree 1) layer by layer
# The remaining 1 or 2 nodes are the roots of minimum height trees
# O(V+E) time, O(V+E) space — iterative leaf removal
if n == 1:
return [0]
graph = [set() for _ in range(n)]
for a, b in edges:
graph[a].add(b)
graph[b].add(a)
leaves = deque(i for i in range(n) if len(graph[i]) == 1)
remaining = n
while remaining > 2:
remaining -= len(leaves)
new_leaves = deque()
for leaf in leaves:
nei = next(iter(graph[leaf]))
graph[nei].remove(leaf)
if len(graph[nei]) == 1:
new_leaves.append(nei)
leaves = new_leaves
return list(leaves)
if __name__ == '__main__':
s = Solution()
assert s.findMinHeightTrees(4, [[1, 0], [1, 2], [1, 3]]) == [1]
assert sorted(s.findMinHeightTrees(6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]])) == [3, 4]
assert s.findMinHeightTrees(1, []) == [0]
assert s.findMinHeightTrees_brute(4, [[1, 0], [1, 2], [1, 3]]) == [1]
assert sorted(s.findMinHeightTrees_brute(6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]])) == [3, 4]
print("All tests passed.") Number Of Connected Components ▼ expand
from typing import List
class Solution:
"""
## 323. Number of Connected Components in an Undirected Graph
You have `n` nodes labeled from `0` to `n - 1` and a list of undirected edges.
Write a function to find the number of connected components in the graph.
### Examples
```text
Input: n = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2
Input: n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output: 1
```
### Constraints
- `1 <= n <= 2000`
- `1 <= edges.length <= 5000`
- `edges[i].length == 2`
- `0 <= ai, bi < n`
- `ai != bi`
- No repeated edges.
"""
def countComponents_dfs(self, n: int, edges: List[List[int]]) -> int:
# O(V+E) time, O(V+E) space
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
visited = [False] * n
def dfs(node: int) -> None:
visited[node] = True
for nei in graph[node]:
if not visited[nei]:
dfs(nei)
count = 0
for i in range(n):
if not visited[i]:
dfs(i)
count += 1
return count
def countComponents(self, n: int, edges: List[List[int]]) -> int:
# Union-Find or DFS: count distinct connected components
# O(V+E) time, O(V) space — Union-Find
parent = list(range(n))
rank = [0] * n
def find(x: int) -> int:
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(x: int, y: int) -> bool:
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
components = n
for a, b in edges:
if union(a, b):
components -= 1
return components
if __name__ == '__main__':
s = Solution()
assert s.countComponents(5, [[0, 1], [1, 2], [3, 4]]) == 2
assert s.countComponents(5, [[0, 1], [1, 2], [2, 3], [3, 4]]) == 1
assert s.countComponents(3, []) == 3
assert s.countComponents_dfs(5, [[0, 1], [1, 2], [3, 4]]) == 2
assert s.countComponents_dfs(5, [[0, 1], [1, 2], [2, 3], [3, 4]]) == 1
print("All tests passed.") Number Of Enclaves ▼ expand
"""
# LC 1020: Number of Enclaves
Count land cells (`1`s) from which you cannot walk off the boundary of the
grid by moving 4-directionally.
## Examples
```text
Input: grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
Output: 3
Input: grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
Output: 0
```
## Constraints
- If no land touches the boundary, all land cells are enclaves.
- If all land connects to the boundary, the answer is 0.
- A grid with no land yields 0.
"""
from typing import List
from collections import deque
class Solution:
# BFS from all boundary land cells, mark reachable land, count unmarked land
def numEnclaves(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
queue = deque()
# seed BFS with all boundary land cells
for r in range(rows):
for c in range(cols):
if (r == 0 or r == rows - 1 or c == 0 or c == cols - 1) and grid[r][c] == 1:
queue.append((r, c))
grid[r][c] = 0 # mark visited by sinking to 0
# flood-fill from boundary - sink all connected land
while queue:
r, c = queue.popleft()
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 0
queue.append((nr, nc))
# remaining 1s are enclaves (not reachable from boundary)
return sum(grid[r][c] for r in range(rows) for c in range(cols))
if __name__ == "__main__":
s = Solution()
assert s.numEnclaves([[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]) == 3
assert s.numEnclaves([[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]) == 0
print("All tests passed.") Number Of Islands ▼ expand
"""
# 200. Number of Islands
## Problem
Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and
`'0'`s (water), return the number of islands. An island is surrounded by water and is
formed by connecting adjacent lands horizontally or vertically.
## Examples
```text
Input: grid = [["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]]
Output: 1
```
```text
Input: grid = [["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]]
Output: 3
```
## Constraints
- `m == grid.length`
- `n == grid[i].length`
- `1 <= m, n <= 300`
- `grid[i][j]` is `'0'` or `'1'`
"""
from typing import List
from collections import deque
class Solution:
def numIslands_dfs(self, grid: List[List[str]]) -> int:
# DFS flood fill: mark visited cells as '0' to avoid revisiting
# Each DFS call from an unvisited '1' cell counts as one island
# O(m*n) time, O(m*n) space — DFS flood fill
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'
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':
dfs(r, c)
count += 1
return count
def numIslands_bfs(self, grid: List[List[str]]) -> int:
# BFS flood fill: same idea but uses a queue instead of recursion
# O(m*n) time, O(min(m,n)) space — BFS flood fill
rows, cols = len(grid), len(grid[0])
count = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
grid[r][c] = '0'
q = deque([(r, c)])
while q:
cr, cc = q.popleft()
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = cr+dr, cc+dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
grid[nr][nc] = '0'
q.append((nr, nc))
return count
def numIslands(self, grid: List[List[str]]) -> int:
# Union-Find: union adjacent land cells; count distinct components
# O(m*n) time, O(m*n) space — Union-Find
rows, cols = len(grid), len(grid[0])
parent = list(range(rows * cols))
rank = [0] * (rows * cols)
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
count = sum(grid[r][c] == '1' for r in range(rows) for c in range(cols))
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
for dr, dc in [(1,0),(0,1)]:
nr, nc = r+dr, c+dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':
if union(r*cols+c, nr*cols+nc):
count -= 1
return count
if __name__ == '__main__':
import copy
s = Solution()
g1 = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]]
g2 = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]
assert s.numIslands(copy.deepcopy(g1)) == 3
assert s.numIslands(copy.deepcopy(g2)) == 1
assert s.numIslands_dfs(copy.deepcopy(g1)) == 3
assert s.numIslands_bfs(copy.deepcopy(g1)) == 3
print("All tests passed.") Number Of Provinces ▼ expand
"""
# LC 547: Number of Provinces
Count connected components in an adjacency matrix where `isConnected[i][j] = 1`
means city `i` and city `j` are directly connected.
## Examples
```text
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3
```
## Constraints
- A single city yields 1 province.
- If all cities are connected, the answer is 1.
- The identity matrix (no connections) yields `n`.
"""
from typing import List
class Solution:
# Approach 1: DFS - treat adjacency matrix as graph, count connected components
def findCircleNum(self, isConnected: List[List[int]]) -> int:
n = len(isConnected)
visited = [False] * n
def dfs(node):
for neighbor in range(n):
if isConnected[node][neighbor] == 1 and not visited[neighbor]:
visited[neighbor] = True
dfs(neighbor)
provinces = 0
for i in range(n):
if not visited[i]:
visited[i] = True
dfs(i)
provinces += 1 # each unvisited node starts a new component
return provinces
# Approach 2: Union-Find
def findCircleNum_uf(self, isConnected: List[List[int]]) -> int:
n = len(isConnected)
parent = list(range(n))
rank = [0] * n
def find(x):
if parent[x] != x:
parent[x] = find(parent[x]) # path compression
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False
# union by rank
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
components = n
for i in range(n):
for j in range(i + 1, n):
if isConnected[i][j] == 1:
if union(i, j):
components -= 1 # merging two components
return components
if __name__ == "__main__":
s = Solution()
assert s.findCircleNum([[1,1,0],[1,1,0],[0,0,1]]) == 2
assert s.findCircleNum([[1,0,0],[0,1,0],[0,0,1]]) == 3
assert s.findCircleNum_uf([[1,1,0],[1,1,0],[0,0,1]]) == 2
assert s.findCircleNum_uf([[1,0,0],[0,1,0],[0,0,1]]) == 3
print("All tests passed.") Open The Lock ▼ expand
"""
# 752. Open the Lock
## Problem
You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots:
`'0', '1', '2', ..., '9'`. The wheels can rotate freely and wrap around (e.g., `'9'`
goes to `'0'` and vice versa). The lock initially starts at `'0000'`.
You are given a list of `deadends` — if the lock displays any of these codes, the wheels
will stop turning. Given a `target` representing the value of the wheels that will unlock
the lock, return the minimum total number of turns required to open the lock, or `-1` if
it is impossible.
## Examples
```text
Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
Explanation: A sequence of valid moves: 0000 -> 1000 -> 1100 -> 1200 -> 1201 -> 1202 -> 0202
```
```text
Input: deadends = ["8888"], target = "0009"
Output: 1
```
```text
Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output: -1
```
## Constraints
- `1 <= deadends.length <= 500`
- `deadends[i].length == 4`
- `target.length == 4`
- `target` will not be in the range of `deadends`
- `target != "0000"`
"""
from typing import List
from collections import deque
class Solution:
def openLock_bfs(self, deadends: List[str], target: str) -> int:
# O(10^4 * 4) time, O(10^4) space — standard BFS
dead = set(deadends)
if '0000' in dead:
return -1
visited = {'0000'}
q = deque([('0000', 0)])
while q:
state, turns = q.popleft()
if state == target:
return turns
for i in range(4):
d = int(state[i])
for delta in (1, -1):
nxt = state[:i] + str((d + delta) % 10) + state[i+1:]
if nxt not in visited and nxt not in dead:
visited.add(nxt)
q.append((nxt, turns + 1))
return -1
def openLock(self, deadends: List[str], target: str) -> int:
# BFS from '0000'; each step turns one wheel up or down
# Skip deadends; return number of steps when target is reached
# O(10^4) time, O(10^4) space — bidirectional BFS
dead = set(deadends)
if '0000' in dead:
return -1
if target == '0000':
return 0
def neighbors(state):
for i in range(4):
d = int(state[i])
for delta in (1, -1):
yield state[:i] + str((d + delta) % 10) + state[i+1:]
front, back = {'0000'}, {target}
visited = {'0000', target}
turns = 0
while front and back:
# always expand the smaller frontier
if len(front) > len(back):
front, back = back, front
turns += 1
nxt = set()
for state in front:
for nb in neighbors(state):
if nb in dead:
continue
if nb in back:
return turns
if nb not in visited:
visited.add(nb)
nxt.add(nb)
front = nxt
return -1
if __name__ == '__main__':
s = Solution()
assert s.openLock_bfs(["0201","0101","0102","1212","2002"], "0202") == 6
assert s.openLock_bfs(["8888"], "0009") == 1
assert s.openLock_bfs(["8887","8889","8878","8898","8788","8988","7888","9888"], "8888") == -1
assert s.openLock(["0201","0101","0102","1212","2002"], "0202") == 6
print("All tests passed.") Pacific Atlantic Water Flow ▼ expand
"""
# 417. Pacific Atlantic Water Flow
## Problem
There is an `m x n` rectangular island that borders both the Pacific Ocean and Atlantic
Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean
touches the island's right and bottom edges.
Water can flow to a neighboring cell (north, south, east, west) if the neighboring cell's
height is less than or equal to the current cell's height. Water can flow from any cell
adjacent to an ocean into that ocean.
Return a list of grid coordinates where water can flow to both the Pacific and Atlantic oceans.
## Examples
```text
Input: heights = [[1,2,2,3,5],
[3,2,3,4,4],
[2,4,5,3,1],
[6,7,1,4,5],
[5,1,1,2,4]]
Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
```
```text
Input: heights = [[1]]
Output: [[0,0]]
```
## Constraints
- `m == heights.length`
- `n == heights[i].length`
- `1 <= m, n <= 200`
- `0 <= heights[i][j] <= 10^5`
"""
from typing import List
from collections import deque
class Solution:
def pacificAtlantic_brute(self, heights: List[List[int]]) -> List[List[int]]:
# O((m*n)^2) time, O(m*n) space — check each cell independently
rows, cols = len(heights), len(heights[0])
def can_reach(r, c, ocean):
visited = set()
q = deque([(r, c)])
visited.add((r, c))
while q:
cr, cc = q.popleft()
if ocean == 'P' and (cr == 0 or cc == 0):
return True
if ocean == 'A' and (cr == rows-1 or cc == cols-1):
return True
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = cr+dr, cc+dc
if 0 <= nr < rows and 0 <= nc < cols and (nr,nc) not in visited and heights[nr][nc] <= heights[cr][cc]:
visited.add((nr, nc))
q.append((nr, nc))
return False
return [[r, c] for r in range(rows) for c in range(cols)
if can_reach(r, c, 'P') and can_reach(r, c, 'A')]
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
# BFS/DFS from Pacific border cells and Atlantic border cells separately
# Cells reachable from both oceans are the answer
# Flow is reversed: water flows from ocean inward (uphill is ok)
# O(m*n) time, O(m*n) space — reverse BFS from ocean borders inward
rows, cols = len(heights), len(heights[0])
def bfs(starts):
visited = set(starts)
q = deque(starts)
while q:
r, c = q.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 (nr,nc) not in visited and heights[nr][nc] >= heights[r][c]:
visited.add((nr, nc))
q.append((nr, nc))
return visited
pacific = bfs([(r, 0) for r in range(rows)] + [(0, c) for c in range(cols)])
atlantic = bfs([(r, cols-1) for r in range(rows)] + [(rows-1, c) for c in range(cols)])
return sorted([r, c] for r, c in pacific & atlantic)
if __name__ == '__main__':
s = Solution()
h = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
expected = [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
assert s.pacificAtlantic(h) == expected
assert s.pacificAtlantic([[1]]) == [[0,0]]
print("All tests passed.") Redundant Connection ▼ expand
from typing import List
class Solution:
"""
## 684. Redundant Connection
In this problem, a tree is an undirected graph that is connected and has no cycles.
You are given a graph that started as a tree with `n` nodes labeled from `1` to `n`,
with one additional edge added. The added edge has two different vertices chosen from
`1` to `n`, and was not an edge that already existed.
Return an edge that can be removed so that the resulting graph is a tree. If there are
multiple answers, return the answer that occurs last in the input.
### Examples
```text
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]
Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
Output: [1,4]
```
### Constraints
- `n == edges.length`
- `3 <= n <= 1000`
- `edges[i].length == 2`
- `1 <= ai, bi <= n`
- `ai != bi`
- No repeated edges.
- The given graph is connected.
"""
def findRedundantConnection_dfs(self, edges: List[List[int]]) -> List[int]:
# O(V*E) time, O(V+E) space
n = len(edges)
graph = [[] for _ in range(n + 1)]
def has_path(src: int, dst: int, visited: set) -> bool:
if src == dst:
return True
visited.add(src)
for nei in graph[src]:
if nei not in visited and has_path(nei, dst, visited):
return True
return False
for a, b in edges:
if has_path(a, b, set()):
return [a, b]
graph[a].append(b)
graph[b].append(a)
return []
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
# Union-Find: process edges in order; the first edge that connects already-connected nodes is redundant
# O(V*alpha(V)) ≈ O(V) time, O(V) space — Union-Find
n = len(edges)
parent = list(range(n + 1))
rank = [0] * (n + 1)
def find(x: int) -> int:
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(x: int, y: int) -> bool:
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
for a, b in edges:
if not union(a, b):
return [a, b]
return []
if __name__ == '__main__':
s = Solution()
assert s.findRedundantConnection([[1, 2], [1, 3], [2, 3]]) == [2, 3]
assert s.findRedundantConnection([[1, 2], [2, 3], [3, 4], [1, 4], [1, 5]]) == [1, 4]
assert s.findRedundantConnection_dfs([[1, 2], [1, 3], [2, 3]]) == [2, 3]
assert s.findRedundantConnection_dfs([[1, 2], [2, 3], [3, 4], [1, 4], [1, 5]]) == [1, 4]
print("All tests passed.") Rotting Oranges ▼ expand
from typing import List
# from collections import deque
class Solution:
"""
**LC 994: Rotting Oranges**
Given an `m x n` grid where each cell has one of three values:
* `0` — empty cell
* `1` — fresh orange
* `2` — rotten orange
Every minute, any fresh orange that is **4-directionally adjacent** to a rotten
orange becomes rotten.
Return the minimum number of minutes until no cell has a fresh orange.
Return `-1` if it is impossible.
Example 1:
```text
Input: grid = [[2,1,1],[1,1,0],[0,1,1]]
Output: 4
```
Example 2:
```text
Input: grid = [[2,1,1],[0,1,1],[1,0,1]]
Output: -1
Explanation: The orange at (2,0) is never reachable — rotting only happens 4-directionally.
```
Example 3:
```text
Input: grid = [[0,2]]
Output: 0
Explanation: No fresh oranges exist at minute 0.
```
**Constraints:**
* `m == grid.length`
* `n == grid[i].length`
* `1 <= m, n <= 10`
* `grid[i][j]` is `0`, `1`, or `2`
"""
def orangesRotting(self, grid: List[List[int]]) -> int:
# Multi-source BFS from all initially rotten oranges simultaneously
# Each BFS level = 1 minute; count fresh oranges; return -1 if any remain
# your code here
pass
if __name__ == "__main__":
sol = Solution()
# Example 1: expected 4
print(sol.orangesRotting([[2, 1, 1], [1, 1, 0], [0, 1, 1]]))
# Example 2: expected -1
print(sol.orangesRotting([[2, 1, 1], [0, 1, 1], [1, 0, 1]]))
# Example 3: expected 0
print(sol.orangesRotting([[0, 2]])) Surrounded Regions ▼ expand
"""
# 130. Surrounded Regions
## Problem
Given an `m x n` matrix `board` containing `'X'` and `'O'`, capture all regions that are
4-directionally surrounded by `'X'`. A region is captured by flipping all `'O'`s into
`'X'`s in that surrounded region.
An `'O'` is not captured if it is on the border or connected to a border `'O'`.
## Examples
```text
Input: board = [["X","X","X","X"],
["X","O","O","X"],
["X","X","O","X"],
["X","O","X","X"]]
Output: [["X","X","X","X"],
["X","X","X","X"],
["X","X","X","X"],
["X","O","X","X"]]
Explanation: The bottom 'O' is on the border and not captured.
```
```text
Input: board = [["X"]]
Output: [["X"]]
```
## Constraints
- `m == board.length`
- `n == board[i].length`
- `1 <= m, n <= 200`
- `board[i][j]` is `'X'` or `'O'`
"""
from typing import List
from collections import deque
class Solution:
def solve_dfs(self, board: List[List[str]]) -> None:
# O(m*n) time, O(m*n) space — DFS mark border-connected O's, then flip rest
rows, cols = len(board), len(board[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != 'O':
return
board[r][c] = 'S'
dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1)
for r in range(rows):
for c in range(cols):
if (r in (0, rows-1) or c in (0, cols-1)) and board[r][c] == 'O':
dfs(r, c)
for r in range(rows):
for c in range(cols):
if board[r][c] == 'O':
board[r][c] = 'X'
elif board[r][c] == 'S':
board[r][c] = 'O'
def solve(self, board: List[List[str]]) -> None:
# Mark all 'O' cells connected to the border as safe (DFS/BFS from borders)
# Flip remaining 'O' cells to 'X'; restore safe cells back to 'O'
# O(m*n) time, O(m*n) space — BFS mark border-connected O's, then flip rest
rows, cols = len(board), len(board[0])
q = deque()
for r in range(rows):
for c in range(cols):
if (r in (0, rows-1) or c in (0, cols-1)) and board[r][c] == 'O':
board[r][c] = 'S'
q.append((r, c))
while q:
r, c = q.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 board[nr][nc] == 'O':
board[nr][nc] = 'S'
q.append((nr, nc))
for r in range(rows):
for c in range(cols):
board[r][c] = 'O' if board[r][c] == 'S' else 'X'
if __name__ == '__main__':
import copy
s = Solution()
board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
expected = [["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
b1 = copy.deepcopy(board)
s.solve_dfs(b1)
assert b1 == expected
b2 = copy.deepcopy(board)
s.solve(b2)
assert b2 == expected
print("All tests passed.") Verifying An Alien Dictionary ▼ expand
"""
# 953. Verifying an Alien Dictionary
## Problem
In an alien language, the alphabet uses the same letters as English but in a possibly
different order. Given a sequence of `words` written in the alien language and the `order`
of the alphabet, return `true` if and only if the given `words` are sorted lexicographically
in this alien language.
## Examples
```text
Input: words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz"
Output: true
Explanation: 'h' comes before 'l' in the alien alphabet.
```
```text
Input: words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz"
Output: false
Explanation: 'd' comes after 'l' in the alien alphabet, so "word" > "world".
```
```text
Input: words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz"
Output: false
Explanation: "apple" > "app" because the first word is longer.
```
## Constraints
- `1 <= words.length <= 100`
- `1 <= words[i].length <= 20`
- `order.length == 26`
- All characters in `words[i]` and `order` are English lowercase letters
"""
from typing import List
class Solution:
def isAlienSorted(self, words: List[str], order: str) -> bool:
# Build order map from alien alphabet; compare adjacent words character by character
# If a word is a prefix of the next word but longer, it's invalid
# O(n*m) time, O(1) space — compare each adjacent pair character by character
rank = {c: i for i, c in enumerate(order)}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i+1]
for j in range(len(w1)):
if j == len(w2): # w2 is prefix of w1 → not sorted
return False
if w1[j] != w2[j]:
if rank[w1[j]] > rank[w2[j]]:
return False
break
return True
if __name__ == '__main__':
s = Solution()
assert s.isAlienSorted(["hello","leetcode"], "hlabcdefgijkmnopqrstuvwxyz") == True
assert s.isAlienSorted(["word","world","row"], "worldabcefghijkmnpqstuvxyz") == False
assert s.isAlienSorted(["apple","app"], "abcdefghijklmnopqrstuvwxyz") == False
assert s.isAlienSorted(["z","z"], "abcdefghijklmnopqrstuvwxyz") == True
print("All tests passed.") Walls And Gates ▼ expand
"""
# 286. Walls and Gates
## Problem
You are given an `m x n` grid `rooms` initialized with these three possible values:
- `-1` — A wall or an obstacle.
- `0` — A gate.
- `INF` — Infinity means an empty room. We use `2147483647` to represent `INF`.
Fill each empty room with the distance to its nearest gate. If it is impossible to reach
a gate, leave it as `INF`. Modify `rooms` in-place.
## Examples
```text
Input:
INF -1 0 INF
INF INF INF -1
INF -1 INF -1
0 -1 INF INF
Output:
3 -1 0 1
2 2 1 -1
1 -1 2 -1
0 -1 3 4
```
```text
Input: rooms = [[-1]]
Output: [[-1]]
```
## Constraints
- `m == rooms.length`
- `n == rooms[i].length`
- `1 <= m, n <= 250`
- `rooms[i][j]` is `-1`, `0`, or `2^31 - 1`
"""
from typing import List
from collections import deque
INF = 2147483647
class Solution:
def wallsAndGates_brute(self, rooms: List[List[int]]) -> None:
# O((m*n)^2) time, O(m*n) space — BFS from each empty room
rows, cols = len(rooms), len(rooms[0])
def bfs(sr, sc):
q = deque([(sr, sc, 0)])
visited = {(sr, sc)}
while q:
r, c, dist = q.popleft()
if rooms[r][c] == 0:
return dist
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 (nr,nc) not in visited and rooms[nr][nc] != -1:
visited.add((nr, nc))
q.append((nr, nc, dist+1))
return INF
for r in range(rows):
for c in range(cols):
if rooms[r][c] == INF:
rooms[r][c] = bfs(r, c)
def wallsAndGates(self, rooms: List[List[int]]) -> None:
# Multi-source BFS from all gates (0 cells) simultaneously
# Each BFS level increments distance; INF cells get updated with shortest distance
# O(m*n) time, O(m*n) space — multi-source BFS from all gates simultaneously
rows, cols = len(rooms), len(rooms[0])
q = deque((r, c) for r in range(rows) for c in range(cols) if rooms[r][c] == 0)
while q:
r, c = q.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 rooms[nr][nc] == INF:
rooms[nr][nc] = rooms[r][c] + 1
q.append((nr, nc))
if __name__ == '__main__':
import copy
s = Solution()
rooms = [[INF,-1,0,INF],[INF,INF,INF,-1],[INF,-1,INF,-1],[0,-1,INF,INF]]
expected = [[3,-1,0,1],[2,2,1,-1],[1,-1,2,-1],[0,-1,3,4]]
r1 = copy.deepcopy(rooms)
s.wallsAndGates(r1)
assert r1 == expected
r2 = copy.deepcopy(rooms)
s.wallsAndGates_brute(r2)
assert r2 == expected
print("All tests passed.") Word Ladder ▼ expand
from typing import List
from collections import deque, defaultdict
class Solution:
"""
## 127. Word Ladder
A transformation sequence from word `beginWord` to word `endWord` using a dictionary
`wordList` is a sequence `beginWord -> s1 -> s2 -> ... -> sk` such that:
- Every adjacent pair of words differs by exactly one letter.
- Every `si` for `1 <= i <= k` is in `wordList`.
- `sk == endWord`
Given `beginWord`, `endWord`, and `wordList`, return the number of words in the
shortest transformation sequence, or `0` if no such sequence exists.
### Examples
```text
Input: beginWord = "hit", endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: "hit" -> "hot" -> "dot" -> "dog" -> "cog"
Input: beginWord = "hit", endWord = "cog",
wordList = ["hot","dot","dog","lot","log"]
Output: 0
```
### Constraints
- `1 <= beginWord.length <= 10`
- `endWord.length == beginWord.length`
- `1 <= wordList.length <= 5000`
- `wordList[i].length == beginWord.length`
- All words consist of lowercase English letters.
- `beginWord != endWord`
- All words in `wordList` are unique.
"""
def ladderLength_bfs(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
# O(m^2 * n) time, O(m^2 * n) space — standard BFS
word_set = set(wordList)
if endWord not in word_set:
return 0
queue = deque([(beginWord, 1)])
visited = {beginWord}
while queue:
word, length = queue.popleft()
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i+1:]
if next_word == endWord:
return length + 1
if next_word in word_set and next_word not in visited:
visited.add(next_word)
queue.append((next_word, length + 1))
return 0
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
# BFS from beginWord; each level = one transformation step
# For each word, try changing each character to find valid neighbors in wordSet
# O(m^2 * n) time, O(m^2 * n) space — bidirectional BFS
word_set = set(wordList)
if endWord not in word_set:
return 0
front, back = {beginWord}, {endWord}
visited = {beginWord, endWord}
length = 1
while front:
length += 1
next_front = set()
for word in front:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i+1:]
if next_word in back:
return length
if next_word in word_set and next_word not in visited:
visited.add(next_word)
next_front.add(next_word)
front = next_front
if len(front) > len(back):
front, back = back, front
return 0
if __name__ == '__main__':
s = Solution()
wl = ["hot", "dot", "dog", "lot", "log", "cog"]
assert s.ladderLength_bfs("hit", "cog", wl) == 5
assert s.ladderLength_bfs("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) == 0
assert s.ladderLength("hit", "cog", wl) == 5
assert s.ladderLength("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) == 0
print("All tests passed.") Word Ladder Ii ▼ expand
"""
# LC 126: Word Ladder II
Find all shortest transformation sequences from `beginWord` to `endWord`,
changing one letter at a time, where each intermediate word must exist in
`wordList`.
## Examples
```text
Input: beginWord = "hit", endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
Input: beginWord = "hit", endWord = "cog",
wordList = ["hot","dot","dog","lot","log"]
Output: []
```
## Constraints
- If `endWord` is not in `wordList`, return `[]`.
- If no transformation path exists, return `[]`.
- The order of returned paths is not significant.
"""
from typing import List
from collections import defaultdict, deque
class Solution:
# BFS to find shortest path length + build parent map, then DFS to collect all paths
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
word_set = set(wordList)
if endWord not in word_set:
return []
# BFS: build layer-by-layer parent map (child -> set of parents)
parents = defaultdict(set)
current_layer = {beginWord}
found = False
while current_layer and not found:
word_set -= current_layer # remove current layer to prevent revisiting
next_layer = set()
for word in current_layer:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
nw = word[:i] + c + word[i+1:]
if nw in word_set:
next_layer.add(nw)
parents[nw].add(word) # record how we reached nw
if nw == endWord:
found = True
current_layer = next_layer
if not found:
return []
# DFS backtrack from endWord to beginWord using parent map
result = []
def dfs(word, path):
if word == beginWord:
result.append(path[::-1])
return
for parent in parents[word]:
path.append(parent)
dfs(parent, path)
path.pop()
dfs(endWord, [endWord])
return result
if __name__ == "__main__":
s = Solution()
result = s.findLadders("hit", "cog", ["hot","dot","dog","lot","log","cog"])
expected = [["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"]]
# path order is non-deterministic; compare as sorted lists of paths
assert sorted(result) == sorted(expected)
assert s.findLadders("hit", "cog", ["hot","dot","dog","lot","log"]) == []
print("All tests passed.") Graph Traversal
BFS, DFS, and Dijkstra's shortest path
Queue — expands level by level
Level 0 Level 1 Level 2 Level 3number = visit order within level
Click a cell to set the start point.