Master this topic with zero to advance depth.
A Graph is the ultimate tool for modeling relationships. From social networks to Google Maps, graphs are everywhere. Mastery involves understanding three things: Representation, Traversal, and Specific Patterns.
| Representation | Memory | Edge Query | Use Case |
|---|---|---|---|
| Adjacency List | Sparse graphs (standard for most interviews). | ||
| Adjacency Matrix | Dense graphs where . | ||
| Edge List | Used in Kruskal's or Bellman-Ford where edge iteration is key. |
| Algorithm | Goal | Complexity |
|---|---|---|
| BFS/DFS | Connectivity / Traversal | |
| Topological Sort | Ordering Dependencies (DAG) | |
| Dijkstra | Shortest Path (Weighted, No Neg) | |
| Bellman-Ford | Shortest Path (Handles Neg Weights) | |
| Floyd-Warshall | All-Pairs Shortest Path | |
| Prim's / Kruskal's | Minimum Spanning Tree (MST) |
[!IMPORTANT] For Grid Problems, avoid building an actual adjacency list. Use directional arrays
dx = {0,0,1,-1}anddy = {1,-1,0,0}to traverse implicitly.
Course Schedule
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_i, b_i] indicates that you must take course b_i first if you want to take course a_i.
Return true if you can finish all courses. Otherwise, return false.
Courses: 2, Prerequisites: [[1, 0]]
Graph: 0 -> 1
Cycle? No. Result: true
Prerequisites: [[1, 0], [0, 1]]
Graph: 0 <-> 1 (Cycle!)
Cycle? Yes. Result: falseExamples
To take course 1 you should have finished course 0. So it is possible.
Intuition
A graph is a Directed Acyclic Graph (DAG) if there are no Back Edges during DFS. We use three states for each node: 0 (unvisited), 1 (visiting/in current path), and 2 (visited). If we encounter a node with state 1, a cycle exists.
1 (visiting).1, return false (cycle).2 (fully processed).true.Detailed Dry Run
pre=[[1,0],[0,1]]
| Node | State | Neighbors | Result |
|---|---|---|---|
| 0 | 1 | [1] | Launch DFS(1) |
| 1 | 1 | [0] | 0 is state 1! CYCLE! |
| Return | - | - | false |
Intuition
Kahn's algorithm uses In-Degrees to peel off nodes with no dependencies. This is the gold standard for Topological Sort as it's iterative and avoids recursion stack issues.
in-degree == 0 to a queue.completed count.completed == numCourses, return true.Detailed Dry Run
num=2, pre=[[1,0]]
| Pop | Queue | In-Degrees | Count |
|---|---|---|---|
| - | [0] | [0:0, 1:1] | 0 |
| 0 | [1] | [1:0] | 1 |
| 1 | [] | - | 2 |
| Result: 2 == 2 (True) |
Number of Islands
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. You may assume all four edges of the grid are all surrounded by water.
Grid:
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
Islands: 3 (Top-left, Middle, Bottom-right)Examples
Three distinct connected groups of '1's.
Intuition
Each land cell '1' can be seen as a node. Adjacent '1's belong to the same connected component. We can use the Disjoint Set Union (DSU) to group these cells and count the number of resulting sets.
union. If they were in different sets, decrement the component count.Detailed Dry Run
Grid: [[1,1], [0,1]]
| Cell | Neighbors | Action | Sets |
|---|---|---|---|
| (0,0) | (0,1) is '1' | Union(0,1) | {0,1}, {1,1} (Count: 2) |
| (0,1) | (1,1) is '1' | Union(1,1) | {0,1,1,1} (Count: 1) |
| Result: 1 |
Intuition
Each cell '1' is a node, and adjacent '1's have edges. We can use a traversal to find Connected Components. When we find a '1', we launch a DFS/BFS to visit all reachable '1's and mark them as '0' ("sinking" the island) to avoid re-visiting.
(r, c).grid[r][c] == '1':
count.(r, c).Detailed Dry Run
Grid: [[1,1], [0,1]]
| i, j | Val | Action | Count |
|---|---|---|---|
| 0,0 | '1' | Start DFS. Sink (0,0), (0,1), (1,1) | 1 |
| 0,1 | '0' | Skip | 1 |
| 1,1 | '0' | Skip | 1 |
| Final Count: 1 |
Clone Graph
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 (List[Node]) of its neighbors.
1 -- 2
| |
4 -- 3
Clone:
1' -- 2'
| |
4' -- 3'Examples
Node 1 is connected to 2 and 4. Node 2 is connected to 1 and 3, etc.
Intuition
BFS uses a queue to visit nodes level-by-level. By using a HashMap to store original -> clone, we can manage complexity and cycles. Every time we encounter a neighbor, if it's not in the map, we clone it and add to the queue.
originalNode -> clonedNode.curr. For each neighbor nbr:
nbr is NOT in the map (unvisited):
nbr to the queue.cloneMap.get(nbr) to the cloneMap.get(curr)'s neighbors list.Detailed Dry Run
1 -- 2
| Map | Queue | Action |
|---|---|---|
| {1:1'} | [1] | Init |
| {1:1', 2:2'} | [2] | Pop 1, clone 2, add 2' to 1' neighbors |
| {1:1', 2:2'} | [] | Pop 2, 1 is in map, add 1' to 2' neighbors |
Intuition
DFS is often more concise for graph cloning as it mimics the structure of the graph through the call stack. The HashMap acts as our "visited" set while also storing the deep copies.
originalNode -> clonedNode.Detailed Dry Run
1 -> [2, 4]
| Map | Call | Action |
|---|---|---|
| {} | DFS(1) | Create 1', Map {1:1'}, Call DFS(2) |
| {1:1'} | DFS(2) | Create 2', Map {1:1', 2:2'}, Call DFS(3) |
| ... | ... | ... |
| {1:1', 2:2', 3:3', 4:4'} | DFS(4) | Return 4' to 1' neighbors list |
Pacific Atlantic Water Flow
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.
Return a list of grid coordinates where rain water can flow to both the Pacific and Atlantic oceans.
Pacific Ocean
| 1 2 2 3 2 |
| 3 2 3 4 4 |
| 2 4 5 3 1 | Atlantic Ocean
| 6 7 1 4 5 |
| 5 1 1 2 4 |Examples
Cells at these coordinates can flow to both oceans.
Intuition
For every single cell in the grid, we start a traversal (DFS) to see if we can reach both the Pacific and Atlantic oceans. A cell can reach an ocean if there is a path of decreasing or equal heights to the boundary.
(r, c):
canReachPacific(r, c) using DFS.canReachAtlantic(r, c) using DFS.(r, c) to results.We repeat the same exploration many times for neighboring cells. Many paths are re-calculated.
Detailed Dry Run
Grid: [[1, 2], [1, 1]]
| Cell | Reach Pac? | Reach Atl? | Result |
|---|---|---|---|
| (0,0) | Yes (Edge) | No (Trapped) | No |
| (0,1) | Yes (Edge) | Yes (Edge) | YES |
Intuition
Instead of asking "Can this cell reach the ocean?", we ask "What cells can the ocean reach?". Water flows from high to low, so if we start at the ocean and go uphill (height >= current), we can find all reachable cells efficiently.
canReachPacific and canReachAtlantic.canReachPacific.canReachAtlantic.Detailed Dry Run
Grid: [[1,2],[1,1]]
| Ocean | Start Cells | Reachable (Uphill) |
|---|---|---|
| Pacific | (0,0),(0,1),(1,0) | (0,0), (0,1), (1,0) |
| Atlantic | (1,1),(0,1),(1,0) | (1,1), (0,1), (1,0) |
| Intersection: (0,1), (1,0), (1,1) |
Rotting Oranges
You are given an m x n grid where each cell can have one of three values:
0 representing an empty cell,1 representing a fresh orange, or2 representing a rotten orange.Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.
Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.
Minute 0: [[2,1,1],[1,1,0],[0,1,1]]
Minute 1: [[2,2,1],[2,1,0],[0,1,1]]
Minute 2: [[2,2,2],[2,2,0],[0,1,1]]
Minute 3: [[2,2,2],[2,2,0],[0,2,1]]
Minute 4: [[2,2,2],[2,2,0],[0,2,2]]
Result: 4Examples
Fresh oranges at (0,1), (0,2), (1,0), (1,1), (2,1), (2,2) rot in 4 minutes.
Intuition
Simulate the rotting process minute by minute. In each minute, look for fresh oranges that are adjacent to any rotten orange. Mark them to rot in the next minute. Repeat this until no new oranges rot.
We iterate through the entire grid multiple times (up to times).
Detailed Dry Run
Grid: [[2,1,1],[0,1,1],[1,0,1]] Min 1: (0,0) rots (0,1). Grid: [[2,2,1],...] Min 2: (0,1) rots (0,2). Grid: [[2,2,2],...] ...
Intuition
Rotting spreads like a wave. BFS is perfect for level-by-level spreading. Instead of one source, we start with all initial rotten oranges in the queue.
fresh oranges and add all rotten orange coordinates to a Queue.fresh > 0:
fresh, add to queue.minutes after processing each level.minutes if fresh == 0, else -1.Detailed Dry Run
Grid: [[2,1,1],[1,1,0],[0,1,1]]
Course Schedule II
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_i, b_i] indicates that you must take course b_i first if you want to take course a_i.
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.
Prerequisites: [[1, 0], [2, 0], [3, 1], [3, 2]]
Graph:
0 -> 1 -> 3
0 -> 2 -> 3
Possible Order: [0, 1, 2, 3] or [0, 2, 1, 3]Examples
To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0.
Intuition
In a DAG, if we perform DFS and record nodes in Post-order (after visiting all neighbors), and then reverse that list, we get a topological sort. We must also check for cycles using the recursion stack.
visited set to track globally visited nodes and a path (recursion stack) set for cycle detection.u in path, return False (cycle found).u in visited, return True (already processed).u to path and visited.False.u from path and append u to a Result List.Detailed Dry Run
num=2, pre=[[1,0]]
| Call | Path | Result | Action |
|---|---|---|---|
| DFS(0) | {0} | [] | Visit 1 |
| DFS(1) | {0,1} | [1] | No neighbors, backtrack |
| - | {0} | [1,0] | Done with 0 |
| Reverse [1,0] -> [0,1] |
Intuition
Kahn's algorithm uses the concept of in-degrees (number of incoming edges). Nodes with 0 in-degree are ready to be taken. We repeatedly take such nodes and 'remove' their outgoing edges, potentially creating new 0 in-degree nodes.
indegree for all nodes.indegree == 0 to a Queue.u, add it to the result list.v of u:
indegree[v].indegree[v] == 0, add v to queue.result length == numCourses, return it; otherwise, a cycle exists.Detailed Dry Run
2 courses, 1 -> 0
Graph Valid Tree
You have a graph of n nodes labeled from 0 to n - 1. You are given an array edges where edges[i] = [a_i, b_i] indicates that there is an undirected edge between nodes a_i and b_i in the graph.
Return true if the edges of the given graph make up a valid tree, and false otherwise.
n=5, edges=[[0,1],[0,2],[0,3],[1,4]]
0
/|\n 1 2 3
|
4
Valid Tree! Result: trueExamples
All nodes are connected and there are no cycles.
Intuition
A graph is a tree if it is connected and has no cycles. We can perform a traversal from any node and check:
visited nodes. For each neighbor, make sure it's not the immediate parent (to handle undirected edges).visited, return False (cycle).visited.size() == n.Detailed Dry Run
n=3, edges=[[0,1],[1,2],[2,0]]
Intuition
This is the most efficient way to check for both conditions. As we add edges, if two nodes already have the same root, adding an edge between them creates a cycle. Connectivity is guaranteed if we process exactly edges without cycles.
len(edges) != n - 1, return False.(u, v):
u and v.False (cycle detected).True.Detailed Dry Run
n=4, edges=[[0,1],[1,2],[2,3]]
Minimum Height Trees
A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.
For a tree of nodes, you can choose any node as the root. The height of the tree is the maximum distance between the root and any leaf.
Return a list of all MHTs' root labels.
n = 4, edges = [[1, 0], [1, 2], [1, 3]]
0
|
1
/ \n2 3
Root 1 has height 1. Other roots have height 2.
Result: [1]Examples
Root 1 gives the minimum height tree.
Intuition
To find the root that minimizes height, we can treat every node as the root one-by-one, perform a BFS/DFS to find its height, and pick the minimum.
For each of the nodes, we traverse elements. This leads to complexity.
Detailed Dry Run
n=3, edges=[[0,1],[1,2]]
Intuition
The center of a tree is limited to 1 or 2 nodes. If we continuously remove the 'outer layers' (leaves), we will eventually converge on the center. This is similar to Kahn's algorithm but for undirected trees.
degree of each node.degree == 1 to a Queue (initial leaves).remainingNodes > 2:
remainingNodes.nbr.degree[nbr].degree[nbr] becomes 1, add nbr to the next level queue.Detailed Dry Run
n=4, edges=[[1,0],[1,2],[1,3]]
Network Delay Time
You are given a network of n nodes, labeled from 1 to n. You are also given times, a list of travel times as directed edges times[i] = (u_i, v_i, w_i), where u_i is the source node, v_i is the target node, and w_i is the time it takes for a signal to travel from source to target.
We will send a signal from a given node k. Return the minimum time it takes for all the n nodes to receive the signal. If it is impossible for all the n nodes to receive the signal, return -1.
Nodes: 4, Times: [[2,1,1],[2,3,1],[3,4,1]], K: 2
(1) <--1-- (2) --1--> (3) --1--> (4)
Signal at 2 reaches 1 and 3 at T=1.
Signal reaches 4 at T=2.
Result: 2Examples
Signal reaches all nodes in 2 units of time.
Intuition
Bellman-Ford is a simpler shortest path algorithm that works by repeatedly relaxing all edges. After iterations, all shortest paths are guaranteed to be found.
dist array with infinity, dist[k] = 0.times:
dist[u] + w < dist[v], update dist[v] = dist[u] + w.max(dist). If any node is still at infinity, return -1.It is computationally slower than Dijkstra but much simpler to implement correctly as it doesn't require a priority queue.
Detailed Dry Run
n=4, k=2, edges=[[2,1,1],[2,3,1],[3,4,1]]
Intuition
This is a single-source shortest path problem on a weighted graph with non-negative weights. Dijkstra's algorithm uses a priority queue to always expand the node with the smallest known distance.
u -> (v, w).dist array with infinity, dist[k] = 0.(time, node) pairs.(t, u). If t > dist[u], continue (stale entry).(v, weight) of u:
dist[u] + weight < dist[v]:
dist[v] = dist[u] + weight and push to queue.max(dist). If any node is unreachable (infinity), return -1.Detailed Dry Run
n=4, k=2, edges=[[2,1,1],[2,3,1],[3,4,1]]
| PQ (t,u) | dist[] | Action |
|---|---|---|
| (0,2) | [inf, 0, inf, inf] | Init |
| (1,1), (1,3) | [1, 0, 1, inf] | Pop 2, relax 1 & 3 |
| (1,3) | [1, 0, 1, inf] | Pop 1 |
| (2,4) | [1, 0, 1, 2] | Pop 3, relax 4 |
| Max(dist) = 2 |
Cheapest Flights Within K Stops
There are n cities connected by some number of flights. You are given an array flights where flights[i] = [from_i, to_i, price_i] indicates that there is a flight from city from_i to city to_i with cost price_i.
You are also given three integers src, dst, and k. Return the cheapest price from src to dst with at most k stops. If there is no such route, return -1.
(0) --100--> (1) --100--> (2)
| ^
|----------500------------|
K = 1:
Path 0 -> 1 -> 2: Price 200. Stops: 1 (OK)
Result: 200
K = 0:
Path 0 -> 2: Price 500. Stops: 0 (OK)
Path 0 -> 1 -> 2: Stops: 1 (FAIL)
Result: 500Examples
0 to 1 then to 2 is cheapest within 1 stop.
Intuition
We can use BFS to explore paths level by level. Each level represents one 'stop'. We stop exploring when we exceed stops.
(node, current_price, current_stops).min_prices array to prune paths that are more expensive than what we've seen at a certain node.(u, price, stops).stops > k, skip.v with flight cost w:
price + w < min_prices[v]:
min_prices[v] = price + w.(v, price + w, stops + 1).Detailed Dry Run
k=1, src=0, dst=2, edges=[[0,1,100],[1,2,100],[0,2,500]]
Intuition
Dijkstra focuses on shortest distance, but here we have a constraint on the number of edges (stops). Bellman-Ford is perfect for this: we run the relaxation process exactly k+1 times to find the shortest path with at most k edges.
prices array with infinity, prices[src] = 0.i from 0 to k:
temp copy of prices to avoid using updated prices from the SAME iteration (level-wise relaxation).(u, v, cost):
prices[u] is not infinity, update temp[v] = min(temp[v], prices[u] + cost).prices = temp.prices[dst] if reachable, else -1.Detailed Dry Run
k=1, src=0, dst=2, flights=[[0,1,100],[1,2,100],[0,2,500]]
| Iter | 0 | 1 | 2 | Actions |
|---|---|---|---|---|
| Init | 0 | inf | inf | - |
| 1 | 0 | 100 | 500 | Relax edges from 0 |
| 2 | 0 | 100 | 200 | Relax edge 1->2 (100+100=200) |
| Result: 200 |
Word Ladder
A transformation sequence from word beginWord to word endWord using a dictionary wordList is a sequence of words such that:
s_i for 1 <= i <= k is in wordList.s_k == endWord.Return the number of words in the shortest transformation sequence, or 0 if no such sequence exists.
Begin: "hit", End: "cog", List: ["hot","dot","dog","lot","log","cog"]
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
Path length: 5Examples
hit -> hot -> dot -> dog -> cog (5 words).
Intuition
The simplest BFS approach is to compare the current word with every word in the dictionary to find neighbors (words with only 1 character difference).
(beginWord, 1).word, dist.w in wordList:
w is not visited and isNeighbor(word, w):
w == endWord, return dist + 1.w as visited and add to queue.If is the number of words, this involves comparisons for every word we visit, leading to total work.
Detailed Dry Run
"hit" -> "cog", ["hot", "dot", "dog", "cog"]
Intuition
Instead of comparing with every word, we can generate all 26 possible neighbors for each character position. This is much faster when the alphabet size is small.
(beginWord, 1).word, dist.word:
word[i] with every letter from 'a' to 'z'.endWord, return dist + 1.Detailed Dry Run
hit -> hot -> dot -> dog -> cog
Shortest Path in Binary Matrix
Given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1.
A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that:
0.Grid:
0 0 0
1 1 0
1 1 0
Path: (0,0) -> (0,1) -> (1,2) -> (2,2)
Length: 4Examples
Clear path found with 4 units length.
Intuition
A standard BFS using a visited set instead of modifying the input grid. This is often preferred in production code to avoid side effects.
visited set to store (r, c).(r, c, distance).Detailed Dry Run
3x3 grid, (0,0) is clear.
Intuition
To optimize space and constant time, we can mark visited cells directly in the grid (e.g., set grid[r][c] = 1). BFS finds the shortest path in an unweighted grid.
(0, 0, 1).grid[0][0] = 1.directions array.0, mark as 1 and queue it.Detailed Dry Run
2x2 grid [[0,1],[1,0]]
Alien Dictionary
There is a new alien language that uses the English alphabet. However, the order among the letters is unknown to you.
You are given a list of strings words from the alien language's dictionary, where the strings in words are sorted lexicographically by the rules of this new language.
Return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language's rules. If there is no solution, return "". If there are multiple solutions, return any of them.
Words: ["wrt","wrf","er","ett","rftt"]
Comparisons:
1. "wrt" vs "wrf" -> t comes before f (t -> f)
2. "wrf" vs "er" -> w comes before e (w -> e)
3. "er" vs "ett" -> r comes before t (r -> t)
4. "ett" vs "rftt" -> e comes before r (e -> r)
Graph: w -> e -> r -> t -> f
Result: "wertf"Examples
From the comparisons, the order is w < e < r < t < f.
Intuition
Alternatively, you can use DFS to perform a topological sort. For each character, we explore all its 'neighbors' and then add the character to a stack.
state map: 0 = unvisited, 1 = visiting (checking for cycles), 2 = visited.u to result list.Detailed Dry Run
["wrt","wrf","er"]
Intuition
The sorted list of words gives us relative ordering of characters. This is a classic Topological Sort problem. We extract dependencies from adjacent words and then apply Kahn's algorithm.
adj and indegree map for all unique characters.word1 and word2:
c1 in word1 and c2 in word2.c1 -> c2 and increment indegree[c2].word2 is a prefix of word1 (e.g., "abc", "ab"), return "" (invalid)."".Detailed Dry Run
["wrt","wrf","er"]
Minimum Cost to Connect All Points
You are given an array points representing integer coordinates of some points on a 2D-plane, where points[i] = [x_i, y_i].
The cost of connecting two points [x_i, y_i] and [x_j, y_j] is the manhattan distance between them: |x_i - x_j| + |y_i - y_j|.
Return the minimum cost to make all points connected. All points are connected if there is exactly one simple path between any two points.
Points: [[0,0],[2,2],[3,10],[5,2],[7,0]]
(3,10)
|
|
(0,0)-(2,2)-(5,2)-(7,0)
Min Cost to connect all points: 20Examples
Points are connected with total manhattan distance 20.
Intuition
Kruskal's algorithm works by sorting all potential edges by weight and adding them one by one if they don't form a cycle (using Union-Find).
It's very intuitive but slightly slower for dense graphs () compared to Prim's .
Detailed Dry Run
Points: [0,0], [2,2], [7,0]
Intuition
For a dense graph (where every point connects to every other point), Prim's algorithm with a simple array is more efficient than Kruskal's or Prim's with a heap.
minDist array where minDist[i] is the shortest distance from the current MST to point i.minDist[0] = 0.u with the smallest minDist.minDist[u] to total cost.u as visited.v, update minDist[v] = min(minDist[v], dist(u, v)).Detailed Dry Run
Points: [0,0], [2,2], [3,10]
Swim in Rising Water
You are given an n x n integer matrix grid where each value grid[i][j] represents the elevation at that point (i, j).
At time t, the water level everywhere is t. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares are at most t. You can swim infinite distances in zero time. You start at the top left square (0, 0) at time 0.
Return the least time until you can reach the bottom right square (n - 1, n - 1).
Grid:
0 1 2
3 4 5
6 7 8
You start at 0. You need time 8 to reach 8 because the bottom-right corner itself is 8.
At t=8, all cells are <= 8, so a path exists.
Result: 8Examples
At time 3, we can swim along the path 0-1-3.
Intuition
Since the possible time answers range from 0 to , we can binary search for the minimum time such that there is a path from to using only cells with elevation .
low = 0, high = N*N - 1.mid (candidate time):
(0,0).grid[nr][nc] <= mid.(N-1, N-1) is reachable, mid is possible; try lower.mid is impossible; try higher.It's very robust and often easier to come up with than the Dijkstra/heap-based approach.
Detailed Dry Run
3x3, grid[0][0]=3, grid[2][2]=5
Intuition
This is a pathfinding problem where the weight of a path is the maximum elevation on that path. We want to find the path with the minimum such weight. This is a variant of Dijkstra where the 'distance' is the max-so-far elevation.
(max_elevation_so_far, r, c).(grid[0][0], 0, 0).visited set to avoid cycles.(t, r, c). This t is the minimum time needed to reach this cell.(r, c) is (n-1, n-1), return t.(nr, nc):
max(t, grid[nr][nc]).Detailed Dry Run
Grid: [[0,2],[1,3]]
Critical Connections in a Network
There are n servers numbered from 0 to n - 1 connected by undirected server-to-server connections forming a network where connections[i] = [a_i, b_i] represents a connection between servers a_i and b_i.
Any connected graph with no cycles is a tree. A critical connection is a connection that, if removed, will make some servers unable to reach some other server.
Return all critical connections in the network in any order.
Servers: 4, Connections: [[0,1],[1,2],[2,0],[1,3]]
0---1---3
\ /
2
Removing [1,3] splits the graph.
Removing [0,1], [1,2], or [2,0] doesn't split it (cycle 0-1-2).
Result: [[1,3]]Examples
[1,3] is the only bridge.
Intuition
Try removing each edge one by one and check if the graph remains connected. If it doesn't, that edge is a critical connection.
Detailed Dry Run
4 nodes, edges [[0,1],[1,2],[2,0],[1,3]]. Remove [1,3]: unreachable. Critical!
Intuition
Tarjan's algorithm uses DFS to find bridges by tracking discovery times and the lowest discovery time reachable via back-edges.
Detailed Dry Run
Graph: 0-1, 1-2, 2-0, 1-3. Bridge condition: low[v] > disc[u]. At node 1, child 3 has low[3]=3, disc[1]=1. 3 > 1 is TRUE. [1,3] is bridge.
Longest Path in a DAG
Given a Directed Acyclic Graph (DAG) and a source vertex src, find the distance to the farthest vertex from src.
In a general graph, finding the longest path is NP-hard. However, in a DAG, it can be solved in linear time using Topological Sort.
Nodes: 0 to 5
Edges: (0,1,5), (0,2,3), (1,3,6), (1,2,2), (2,4,4), (2,5,2), (2,3,7), (3,4,-1), (4,5,-2)
Topological Order: 0, 1, 2, 3, 4, 5
Relax edges in this order to find max distances.Examples
Distances from node 1: to 3 is 9 (1->2->3 is 2+7), to 4 is 8, etc.
Intuition
The longest path from a node u is max(weight(u, v) + longestPath(v)) for all neighbors v. Since the graph is a DAG, we can use memoization to avoid redundant computations.
dfs(u) as the longest path starting from node u.dfs(u):
u is already in memo, return memo[u].max_dist = 0 (or some base case).v with weight w:
max_dist = max(max_dist, w + dfs(v)).max_dist.Detailed Dry Run
src=1. Edges from 1: (3,6), (2,2).
Intuition
Processing nodes in topological order allows finding the longest path in .
Detailed Dry Run
Order: 0,1,2,3,4,5. Relax edges from 1. 1->2->3 is longer than 1->3 directly.
Reconstruct Itinerary
You are given a list of airline tickets where tickets[i] = [from_i, to_i] represent the departure and the arrival airports of one flight. Reconstruct the itinerary in order and return it.
All of the tickets belong to a man who departs from "JFK", thus, the itinerary must begin with "JFK". If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string.
Tickets: [["MUC","LHR"], ["JFK","MUC"], ["SFO","SJC"], ["LHR","SFO"]]
Graph: JFK -> MUC -> LHR -> SFO -> SJC
Result: ["JFK", "MUC", "LHR", "SFO", "SJC"]Examples
Standard path starting from JFK.
Intuition
This is finding an Eulerian Path in a directed graph. An Eulerian Path visits every edge exactly once. Hierholzer's algorithm finds this by performing DFS and appending nodes to the result only when they have no more outgoing edges.
Detailed Dry Run
JFK -> [MUC, SFO], MUC -> [LHR], LHR -> [JFK]
Shortest Path to Get All Keys
You are given an m x n grid grid where:
'.' is an empty cell.'#' is a wall.'@' is the starting point.You start at the starting point and one move consists of walking one space in one of the four cardinal directions. You cannot walk outside the grid, or through a wall. If you walk over a key, you pick it up. If you walk over a lock and have the matching key, you can pass through it.
Return the lowest number of moves to acquire all keys. If it's impossible, return -1.
Grid:
@ . a . #
# # # . #
b . A . B
Starting at @. Path:
1. Pick 'a'
2. Use 'a' to open 'A'
3. Pick 'b'
Done! Return shortest path length.Examples
Shortest path to pick up all keys 'a' and 'b'.
Intuition
The 'state' of our BFS is not just our position (r, c), but also which keys we possess. We can represent the set of obtained keys as a bitmask. This turns the problem into a standard shortest path BFS in a state space of (r, c, mask).
@ and count total number of keys K.(1 << K) - 1.(r, c, mask, distance).visited[r][c][mask] tracks seen states.newMask = mask | (1 << (key - 'a')).newMask == targetMask, return dist.Detailed Dry Run
2 keys. Target mask = 11 (3). Start (0,0) mask 00.
Find the City With the Smallest Number of Neighbors at a Threshold Distance
There are n cities numbered from 0 to n-1. Given the array edges where edges[i] = [from_i, to_i, weight_i] represents a bidirectional weighted edge between cities from_i and to_i, and given the integer distanceThreshold.
Return the city with the smallest number of cities that are reachable through some path and whose distance is at most distanceThreshold. If there are multiple such cities, return the city with the greatest number.
Cities 4, Edges: [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], Threshold 4
0 --3-- 1 --1-- 2
\ /
4---3
(1)
City 0: Neighbors {1,2} (Dist 3, 4) - Count 2
City 3: Neighbors {1,2} (Dist 2, 1) - Count 2
... compare all, find min count.Examples
City 3 has 2 neighbors within distance 4, same as city 0, but 3 > 0.
Intuition
This problem requires finding all-pairs shortest paths to count how many cities are within the threshold for each city. Floyd-Warshall is the standard algorithm for all-pairs shortest paths in a small number of vertices ().
dist matrix of size with infinity, and dist[i][i] = 0.(u, v, w), set dist[u][v] = dist[v][u] = w.k, i, j to update dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]).i, count how many j have dist[i][j] <= distanceThreshold.Detailed Dry Run
N=4, Threshold=4
Help us improve! Report bugs or suggest new features on our Telegram group.