INVARIANT · GRAPHS · TRAVERSAL
01
00/26
01 / COVER STEP 15 · GRAPHS
INVARIANT · STEP 15 · DECK 1 OF 3
GRAPH TRAVERSAL

Two traversals, and the twenty-four problems that are secretly just those two traversals. Every lecture is wrapped: primed by an intro, checked by drills, then applied to the sheet problems it unlocks.

26Problems
22Units
22Lectures
5Visualisers
← → ↑ ↓  or  W A S D  navigate SPACE next   DOUBLE-CLICK to advance I index   G goto problem   P predict H hide solutions   T close video   F fullscreen
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

This is not a list of problems. It is 22 learning units, and every one runs the same four beats. Go in order and the problems stop feeling random.

01 INTROWhat the concept is, and what to watch for
02 VIDEOThe lecture, full-width in theatre mode
03 DRILLS2–4 questions checking the lecture landed
04 PROBLEMSThe sheet problems that concept unlocks

Moving around

← ↑Back a slide — or A / W → ↓Forward — or D / S / Space 2×clickDouble-click the right side to advance, left to go back. A single click never moves the deck. IThe index: every problem, clickable, with your progress GJump straight to a problem by its number FFullscreen

While you study

HHide solutions — blurs code and steps so you try first PPredict mode: call the next step before the animation plays it TClose the video — Esc works too ☐ ★Mark solved, or star to revisit. Both are saved automatically.

26 PROBLEMS · 15 LINK TO LEETCODE · THE REST ARE CONCEPTS THE DRILLS COVER

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 26 PROBLEMS

Click any problem to jump straight to it. Solved ones turn green. Press I from anywhere to come back here.

LEARNING · 05
PROBLEMS ON BFS / DFS · 14
TOPO SORT & PROBLEMS · 07
SOLVED HAS A LEETCODE LINK CONCEPT — DRILLS ONLY
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

WHICH TRAVERSAL, AND WHY

Almost every problem in this deck is BFS or DFS wearing a costume. The statement tells you which one — and getting that right is most of the work.

HOW MANY SEPARATE PIECES?

Islands, provinces, friend circles — anything asking how many groups.

Outer loop + DFS or BFSO(V + E)
FEWEST STEPS, EVERY STEP EQUAL

Shortest path where each move costs the same. Never DFS for this.

BFS, layer by layerO(V + E)
SPREADING FROM SEVERAL PLACES

Rotting oranges, nearest 1, fire spreading — many starting points at once.

Multi-source BFSO(V + E)
IS THERE A CYCLE?

Undirected needs the parent skip; directed needs the recursion stack.

DFS (parent / on-stack)O(V + E)
ORDER WITH PREREQUISITES

Course schedule, build order, alien dictionary — X must come before Y.

Topological sort · Kahn'sO(V + E)
SPLIT INTO TWO GROUPS

Two teams, two colours, no conflict between members of the same side.

Bipartite check by colouringO(V + E)
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
05 / INTRO UNIT 01 · WHAT IS A GRAPH

UNIT 01 — WHAT IS A GRAPH

A graph is nodes, and the pairs of nodes that are joined. Everything else — directed, weighted, cyclic, connected — is a modifier bolted onto that one idea. This lecture is vocabulary, and the vocabulary is what makes every later problem statement readable.

THE QUESTION THIS LECTURE ANSWERS

WHAT EXACTLY IS A GRAPH, AND WHAT ARE THE WORDS FOR ITS PARTS?

NODE / VERTEXEDGEDEGREEDIRECTEDWEIGHTEDCYCLE
WHAT TO WATCH FOR
  • 01DEGREE SUMMED OVER ALL NODES EQUALS 2 × EDGES, ALWAYS
  • 02A TREE IS JUST A CONNECTED GRAPH WITH N NODES AND N−1 EDGES
  • 03NODES MAY BE 0-INDEXED OR 1-INDEXED — THE SHEET USES BOTH
  • 04CYCLIC VS ACYCLIC IS THE SPLIT THAT DECIDES HALF THESE ALGORITHMS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
06 / VIDEO UNIT 01 · WHAT IS A GRAPH

G-1 · Introduction to Graph | Types | Conventions

GRAPH SERIES
WHAT IS A GRAPH
RUNTIME 13:43
AFTER THIS → 3 DRILLS · PROBLEM #01
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
07 / DRILL UNIT 01 · WHAT IS A GRAPH

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

In an undirected graph with E edges, what do all the node degrees add up to?

Every edge has two endpoints, and contributes 1 to the degree of each. Sum over all nodes and you have counted every edge twice — so the total is 2E. This is also why an undirected adjacency list holds 2E entries, not E.

DRILL 02 · RECALL

A connected undirected graph has N nodes and exactly N−1 edges. What is it?

Connected + N−1 edges = tree. Add any edge and you create exactly one cycle; remove any edge and it splits in two. Recognising this instantly matters because tree problems drop the visited array in favour of a simple parent check.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
08 / DRILL UNIT 01 · WHAT IS A GRAPH

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Why does 0-indexed vs 1-indexed bite harder in graph problems than in array problems?

You allocate two N-sized structures up front. Size them wrong and node N either never gets visited or writes past the end — and neither shows up as an obvious crash on the sample case. Read the constraints for the indexing before you allocate.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
09 / CONCEPT #01 · REPRESENTATION · EASY

Introduction to Graph

EASY representation CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

You are being handed edges and asked anything at all about connectivity. Before you write a line, settle three things: directed or not, weighted or not, 0- or 1-indexed.

INTUITION

This is the vocabulary problem. The only real work is reading the input format correctly and sizing your structures to match. Getting N and the indexing right here prevents a whole class of silent bugs later.

STEPS
  1. Read V and E
  2. Decide directed vs undirected from the statement
  3. Size adj[] as V or V+1 to match the indexing
  4. Read E pairs and build
↕ SCROLL
int V, E;  cin >> V >> E;
// 1-indexed nodes → size V+1, not V
vector<int> adj[V + 1];

for (int i = 0; i < E; i++) {
    int u, v;  cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u);   // omit if DIRECTED
}
TIMEO(V + E)one pass over the edge list
SPACEO(V + 2E)each undirected edge stored twice
TRAP

If nodes are 1-indexed, declare adj[V+1]. Declaring adj[V] and then touching node V writes off the end — it usually will not crash, it will just corrupt something adjacent and fail on a case you cannot reproduce.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
10 / INTRO UNIT 02 · REPRESENTATION

UNIT 02 — REPRESENTATION

Two ways to answer the only question a traversal ever asks: who are my neighbours? A matrix answers in O(1) but costs O(N²) memory. A list costs O(2E) and answers in O(degree). At the sheet's constraints, one of these is simply dead.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU STORE A GRAPH SO YOUR CODE CAN ASK 'WHO ARE MY NEIGHBOURS?'

ADJACENCY MATRIXADJACENCY LISTvector<int> adj[]pair<int,int>
WHAT TO WATCH FOR
  • 01ADJACENCY MATRIX IS O(N²) SPACE — DEAD ABOVE ROUGHLY N = 10⁴
  • 02ADJACENCY LIST FOR UNDIRECTED STORES EACH EDGE TWICE
  • 03FOR A DIRECTED GRAPH YOU PUSH ONE WAY ONLY
  • 04WEIGHTED GRAPHS HOLD PAIRS, NOT PLAIN INTS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
11 / VIDEO UNIT 02 · REPRESENTATION

G-2 · Graph Representation | Two Ways

GRAPH SERIES
REPRESENTATION
RUNTIME 16:04
AFTER THIS → 3 DRILLS · PROBLEM #02
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
12 / DRILL UNIT 02 · REPRESENTATION

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Space taken by an adjacency list for an undirected graph with N nodes and E edges?

N buckets, plus two entries per edge because u lands in v's list and v lands in u's. That 2E is the whole reason the list wins: at N = 10⁵ and E = 10⁵ a list holds ~3×10⁵ ints while a matrix would demand 10¹⁰ cells.

DRILL 02 · BUG

This builds a DIRECTED graph. One line does not belong. Which?

for (auto &e : edges) {
  adj[e.first].push_back(e.second);
  adj[e.second].push_back(e.first);
}

Pushing both ways is the undirected build. In a directed graph u→v does not imply v→u, and adding the reverse edge quietly destroys every algorithm downstream — topological sort finds no valid order, cycle detection fires on every pair. One line, total collapse.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
13 / DRILL UNIT 02 · REPRESENTATION

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Constraints say N = 10⁵ and E = 2×10⁵. Which representation, and why?

10⁵ squared is 10¹⁰ cells. At one byte each that is 10 GB against a typical 256 MB limit. The list needs ~4×10⁵ ints. Read the constraint, then pick the structure — the same move as the complexity ladder, applied to memory.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
14 / CONCEPT #02 · REPRESENTATION · EASY

Graph Representation

EASY representation CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Constraints tell you which representation is even legal. N above ~10⁴ kills the matrix outright — O(N²) memory is the deciding factor, not lookup speed.

INTUITION

A matrix answers is u joined to v? in O(1) but costs O(N²) memory. A list answers who are u's neighbours? in O(degree) and costs O(2E). Traversals only ever ask the second question, which is why the list wins almost everywhere on this sheet.

STEPS
  1. Read N and the constraint on N
  2. N ≤ 10³ and dense → matrix is fine
  3. N large or sparse → adjacency list
  4. Weighted → store pair{neighbour, weight}
BRUTEO(N²) space
OPTIMALO(V + 2E)
↕ SCROLL
// ADJACENCY LIST — the default choice
vector<int> adj[N + 1];
adj[u].push_back(v);
adj[v].push_back(u);        // undirected only

// WEIGHTED — store the weight alongside
vector<pair<int,int>> wadj[N + 1];
wadj[u].push_back({v, w});

// ADJACENCY MATRIX — only when N is small
vector<vector<int>> m(N, vector<int>(N, 0));
m[u][v] = m[v][u] = 1;
TIMEO(V + 2E)build is one pass over edges
SPACEO(V + 2E)two entries per undirected edge
TRAP

For a directed graph push one way only. Adding the reverse edge out of habit silently converts the problem into a different one — topological sort then finds no valid ordering and cycle detection fires on every single edge.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
15 / INTRO UNIT 03 · CONNECTED COMPONENTS

UNIT 03 — CONNECTED COMPONENTS

The picture in the problem statement may be several graphs wearing a trenchcoat. Nothing guarantees you can reach every node from node 1 — and that single fact is why every traversal you will write is wrapped in a loop over all nodes.

THE QUESTION THIS LECTURE ANSWERS

WHY DOES EVERY GRAPH TRAVERSAL START WITH A LOOP OVER ALL NODES?

COMPONENTVISITED ARRAYOUTER LOOPDISCONNECTED
WHAT TO WATCH FOR
  • 01ONE DRAWING CAN BE SEVERAL DISCONNECTED COMPONENTS
  • 02THE VISITED ARRAY IS SHARED ACROSS ALL COMPONENTS — NEVER RESET IT
  • 03THE OUTER LOOP FIRES A TRAVERSAL ONLY ON STILL-UNVISITED NODES
  • 04COUNTING THOSE FIRINGS COUNTS THE COMPONENTS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
16 / VIDEO UNIT 03 · CONNECTED COMPONENTS

G-4 · What are Connected Components?

GRAPH SERIES
CONNECTED COMPONENTS
RUNTIME 07:07
AFTER THIS → 3 DRILLS · PROBLEM #03
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
17 / DRILL UNIT 03 · CONNECTED COMPONENTS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Why wrap BFS/DFS in a for-loop over every node instead of just calling it on node 0?

A single traversal only ever reaches one component. If the graph is in three pieces, one call visits one piece and returns, and you silently answer for a third of the input. The outer loop is the cheapest insurance in graph code.

DRILL 02 · BUG

This counts connected components. What breaks it?

int count = 0;
for (int i = 0; i < N; i++) {
  vector<int> vis(N, 0);   // fresh each iteration
  if (!vis[i]) { dfs(i, adj, vis); count++; }
}

Declaring vis inside the loop throws away everything the previous traversal learned. Every node then passes the !vis[i] test, you fire N traversals, and a graph with 2 components reports N. The visited array must outlive the loop — that is the entire point of it.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
18 / DRILL UNIT 03 · CONNECTED COMPONENTS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRACE

A 7-node graph has edges 1-2, 2-3, 5-6. Nodes 4 and 7 have no edges. How many components?

{1,2,3}, {5,6}, {4}, {7} — four. An isolated node with zero edges is still a component, and forgetting that is the classic off-by-two on Number of Provinces. Every node belongs to exactly one component, always.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
19 / CONCEPT #03 · REPRESENTATION · MED

Connected Components

MED representation CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

The statement says a graph but never promises it is one piece. Any phrase like groups, islands, provinces or separate is asking you to count components.

INTUITION

Fire a traversal from every node that no previous traversal reached. Each firing consumes exactly one component, so the number of firings is the number of components. One shared visited array does all the bookkeeping.

STEPS
  1. Allocate vis[] once, outside every loop
  2. Loop i from 0 to V−1
  3. If i is unvisited, run BFS/DFS from it
  4. Increment the counter on each firing
↕ SCROLL
int countComponents(int V, vector<int> adj[]) {
    vector<int> vis(V, 0);   // OUTSIDE the loop
    int count = 0;

    for (int i = 0; i < V; i++) {
        if (!vis[i]) {
            dfs(i, adj, vis);
            count++;              // one firing = one component
        }
    }
    return count;
}
TIMEO(V + E)every node and edge touched once total
SPACEO(V)visited array plus stack
TRAP

Declaring vis inside the outer loop resets everything each iteration, so every node looks unvisited and the count comes back as V. The visited array must outlive the loop — that is the entire reason it exists.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
20 / INTRO UNIT 04 · BFS

UNIT 04 — BFS

Visit everything one hop from the source, then everything two hops away, then three. BFS explores in rings. That ring structure is not a side effect — it is why BFS hands you shortest paths for free on an unweighted graph.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU VISIT A GRAPH IN LAYERS, OUTWARD FROM A SOURCE?

ADJACENCY LISTFRONTIERVISITED ARRAYFIFOLEVEL / RING
WHAT TO WATCH FOR
  • 01THE VISITED MARK GOES IN ON PUSH, NOT ON POP
  • 02THE QUEUE HOLDS ONLY THE FRONTIER — NEVER THE WHOLE GRAPH
  • 03THE OUTER LOOP OVER ALL NODES IS FOR DISCONNECTED COMPONENTS
  • 04O(V + E) IS ADDITIVE BECAUSE EACH EDGE IS SEEN A FIXED NUMBER OF TIMES
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
21 / VIDEO UNIT 04 · BFS

G-5 · Breadth-First Search

GRAPH SERIES
BFS
RUNTIME 19:39
AFTER THIS → 4 DRILLS · PROBLEM #04
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
22 / DRILL UNIT 04 · BFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

In BFS, at what moment is a node marked visited?

Mark on push. If you wait until pop, a node can sit in the queue while a second neighbour reaches it and pushes it again — the same node enters twice, gets processed twice, and on a dense graph the queue blows past O(V). Marking at push time is what makes the visited array an admission ticket rather than a receipt.

DRILL 02 · TRACE

Mid-traversal the queue reads [4, 3, 5] front-to-back. You pop once and node 4 pushes nothing new. What is at the front now?

A queue is FIFO. 3 entered before 5, so 3 leaves before 5 — regardless of which node discovered them. This is the single line between BFS and DFS: swap the queue for a stack and the same code becomes depth-first.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
23 / DRILL UNIT 04 · BFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

One line here breaks the traversal. Which is it?

// BFS inner loop
for (auto it : adj[node]) {
  if (!vis[it]) {
    q.push(it);
    vis[it] = 1;  // ← moved below the push?
  }
}

Trick question — this code is fine. Both lines run before the loop moves on, so ordering within the if-block changes nothing. The real bug is marking visited after the pop, in the outer loop — a different place entirely. Knowing which rearrangements are harmless is as useful as knowing which are fatal.

DRILL 02 · TRANSFER

BFS is O(V + E), not O(V × E). Why is the E term additive?

Each node is popped exactly once, and popping it scans its adjacency list once. Sum those lists over every node and you have counted each undirected edge twice — 2E total, across the entire traversal, not per node. That is why the terms add. The same argument gives DFS the same bound.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
24 / MECHANISM UNIT 04 · BFS · CODE MIRRORED

BFS — THE QUEUE IS THE ALGORITHM

Step it and watch the code line light up beside the graph. Then turn on PREDICT and call the next pop yourself before the deck shows you.

VISITED
QUEUE FIFO
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
25 / CONCEPT #04 · TRAVERSAL · MED

Breadth-First Search (BFS)

MED traversal CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Anything about levels, layers, minimum steps or shortest path on an unweighted graph. If every edge costs the same, BFS is already the shortest-path algorithm — you do not need Dijkstra.

INTUITION

The queue holds the frontier: everything discovered but not yet expanded. Draining it in FIFO order means you finish distance-k nodes before you touch any distance-(k+1) node. That ordering is what makes the first time you reach a node also the shortest way to reach it.

STEPS
  1. Push the source, mark it visited immediately
  2. While the queue is non-empty, pop the front
  3. For each unvisited neighbour: mark visited, then push
  4. Wrap in the component loop if the graph may be disconnected
↕ SCROLL
vector<int> bfs(int V, vector<int> adj[]) {
    vector<int> vis(V, 0), order;
    queue<int> q;

    q.push(0);  vis[0] = 1;      // mark ON PUSH

    while (!q.empty()) {
        int node = q.front();  q.pop();
        order.push_back(node);

        for (auto it : adj[node]) {
            if (!vis[it]) {
                vis[it] = 1;
                q.push(it);
            }
        }
    }
    return order;
}
TIMEO(V + E)each node popped once, each edge scanned twice
SPACEO(V)queue and visited array, both at most V
TRAP

Mark visited on push, not on pop. Marking at pop lets a node get pushed by two different neighbours before it is ever popped — it then enters the queue twice, is processed twice, and on a dense graph the queue grows past O(V).

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
26 / INTRO UNIT 05 · DFS

UNIT 05 — DFS

Go as deep as you can, and only back up when you are stuck. DFS is BFS with the queue swapped for a stack — and you rarely write that stack, because the call stack already is one. Same O(V + E), completely different shape.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU WALK A GRAPH BY GOING AS DEEP AS POSSIBLE BEFORE BACKING UP?

CALL STACKRECURSIONBACKTRACKDEPTHLIFO
WHAT TO WATCH FOR
  • 01THE CALL STACK IS THE STACK — RECURSION HIDES IT FROM YOU
  • 02MARK VISITED ON ENTRY, BEFORE THE NEIGHBOUR LOOP RUNS
  • 03SAME O(V + E) AS BFS — THE ORDER DIFFERS, THE COST DOES NOT
  • 04RECURSION DEPTH CAN REACH N — A 10⁵ PATH GRAPH WILL OVERFLOW
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
27 / VIDEO UNIT 05 · DFS

G-6 · Depth-First Search

GRAPH SERIES
DFS
RUNTIME 20:16
AFTER THIS → 3 DRILLS · PROBLEM #05
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
28 / DRILL UNIT 05 · DFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

In the recursive DFS, where does vis[node] = 1 go?

On entry. Mark it after the loop and a cycle sends you straight back into a node still marked unvisited — infinite recursion, stack overflow. The visited mark exists to say I am already handling this, which must be true the moment you arrive, not once you leave.

DRILL 02 · TRACE

On the deck's graph, DFS from node 1 takes neighbours in ascending order: 1, 2, 4, 7 … which node is fifth?

DFS commits. At 7 it does not look sideways at 8 or upward at 5 — it takes 11 and keeps descending until the branch is exhausted. Full order: 1 2 4 7 11 12 8 5 9 10 6 3 13.

Now compare BFS on this same graph: 1 2 3 4 5 6 7 8 9 10 11 12 13 — dead in order, because the nodes are numbered level by level. Node 3 is second under BFS and twelfth under DFS. That gap is the whole difference between the two.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
29 / DRILL UNIT 05 · DFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Why is recursive DFS risky at N = 10⁵ when BFS at the same N is fine?

A 10⁵-node path graph drives recursion 10⁵ frames deep. The call stack is typically ~1–8 MB and blows well before that; BFS's queue lives on the heap and is fine. Same complexity, different memory region — which is exactly when you rewrite DFS with an explicit stack.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
30 / MECHANISM UNIT 05 · DFS · CODE MIRRORED

DFS — THE CALL STACK IS THE ALGORITHM

The same graph, the same code mirror. Watch the stack grow on the way down and unwind on the way back — then compare the visit order against BFS.

VISITED
QUEUE LIFO
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
31 / CONCEPT #05 · TRAVERSAL · MED

Depth-First Search (DFS)

MED traversal CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Anything about paths, reachability, connected regions, or where you need to finish exploring one branch fully before starting another — cycle detection and topological sort are both DFS underneath.

INTUITION

Go deep, back up only when stuck. The recursion's call stack is the stack, which is why DFS code is so much shorter than BFS. Same O(V + E) cost, completely different visit order.

STEPS
  1. Mark the node visited on entry, before the neighbour loop
  2. Recurse into each unvisited neighbour in turn
  3. Returning unwinds one frame — that is the backtrack
  4. Wrap in the component loop for disconnected graphs
↕ SCROLL
void dfs(int node, vector<int> adj[],
         vector<int>& vis, vector<int>& order) {

    vis[node] = 1;            // mark ON ENTRY
    order.push_back(node);

    for (auto it : adj[node]) {
        if (!vis[it]) {
            dfs(it, adj, vis, order);
        }
    }
    // returning here pops the frame — the backtrack
}
TIMEO(V + E)identical bound to BFS — only the order differs
SPACEO(V)recursion depth, which can reach V on a path graph
TRAP

At V = 10⁵ a path-shaped graph drives recursion 10⁵ frames deep and overflows the call stack, while BFS at the same size is fine because its queue lives on the heap. Same complexity, different memory region — that is when you rewrite DFS with an explicit stack.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
32 / INTRO UNIT 06 · NUMBER OF PROVINCES

UNIT 06 — NUMBER OF PROVINCES

The first real sheet problem, and it is unit 03 with a counter bolted on. A province is a connected component. The only genuinely new thing is the input format: you are handed an adjacency matrix, not a list.

THE QUESTION THIS LECTURE ANSWERS

HOW MANY SEPARATE PIECES IS THIS GRAPH IN?

PROVINCEADJACENCY MATRIXCOMPONENT COUNT
WHAT TO WATCH FOR
  • 01THE INPUT IS AN ADJACENCY MATRIX — isConnected[i][j] == 1 MEANS AN EDGE
  • 02THE DIAGONAL IS ALWAYS 1; A NODE IS CONNECTED TO ITSELF
  • 03THE ANSWER IS HOW MANY TIMES THE OUTER LOOP FIRES A TRAVERSAL
  • 04CONVERT TO A LIST, OR JUST SCAN ROW i — BOTH ARE FINE AT N ≤ 200
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
33 / VIDEO UNIT 06 · NUMBER OF PROVINCES

G-7 · Number of Provinces | Connected Components

GRAPH SERIES
NUMBER OF PROVINCES
RUNTIME 15:29
AFTER THIS → 3 DRILLS · PROBLEM #06
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
34 / DRILL UNIT 06 · NUMBER OF PROVINCES

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What exactly does the province counter count?

Each time the outer loop finds a still-unvisited node, that node belongs to a component nothing has reached yet — so you fire a traversal and add one. The count of traversals is the count of components. This is the same skeleton you will reuse for Number of Islands.

DRILL 02 · TRACE

N = 3 and isConnected = [[1,1,0],[1,1,0],[0,0,1]]. How many provinces?

Nodes 0 and 1 are joined; node 2 is alone. That is two provinces. The diagonal 1s are self-loops and carry no information — reading them as real edges is the usual way people talk themselves into answering 3.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
35 / DRILL UNIT 06 · NUMBER OF PROVINCES

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

At N ≤ 200, why is scanning the matrix row directly acceptable instead of building an adjacency list?

N² at N = 200 is 40,000 — nothing. Read the constraint first and the representation question answers itself. At N = 10⁵ the same code would be 10¹⁰ and you would have no choice but to convert.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
36 / PROBLEM #06 · COMPONENTS · MED

Number of Provinces

MED components ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Provinces, groups, circles of friends — plus the input arrives as an N × N matrix rather than an edge list. That matrix shape is the giveaway.

INTUITION

A province is just a connected component wearing a different word. Run the component-counting skeleton; the only adjustment is that neighbours come from scanning row i of the matrix instead of reading a list.

STEPS
  1. Allocate vis[] of size N
  2. Loop i from 0 to N−1
  3. If i unvisited, DFS from it and increment the count
  4. Inside DFS, scan row i for every j where isConnected[i][j] == 1
BRUTEO(N³)
OPTIMALO(N²)
↕ SCROLL
int findCircleNum(vector<vector<int>>& isConnected) {
    int n = isConnected.size(), count = 0;
    vector<int> vis(n, 0);

    for (int i = 0; i < n; i++) {
        if (!vis[i]) { dfs(i, isConnected, vis); count++; }
    }
    return count;
}

void dfs(int node, vector<vector<int>>& m, vector<int>& vis) {
    vis[node] = 1;
    for (int j = 0; j < m.size(); j++) {
        if (m[node][j] && !vis[j]) dfs(j, m, vis);
    }
}
TIMEO(N²)each matrix cell inspected once
SPACEO(N)visited array plus recursion
TRAP

The diagonal is always 1 — every node is trivially connected to itself. That is not an edge and carries no information; reading it as one is how a 2-province answer becomes 3.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #06 · NUMBER OF PROVINCES

G-7 · Number of Provinces | Connected Components

The walkthrough for #06 Number of Provinces. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-7 · Number of Provinces | Connected Components
RUNTIME 15:29
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
37 / INTRO UNIT 07 · COMPONENTS IN A MATRIX

UNIT 07 — COMPONENTS IN A MATRIX

Nobody hands you an adjacency list here — just a grid of characters. The grid is the graph. Every cell is a node, and its four neighbours are its edges. Once you see that, half the matrix problems on this sheet collapse into traversals you already know.

THE QUESTION THIS LECTURE ANSWERS

WHAT IF NOBODY GIVES YOU A GRAPH — JUST A GRID?

IMPLICIT GRAPHCELL AS NODE4-DIRECTIONALDELTA ARRAYBOUNDS CHECK
WHAT TO WATCH FOR
  • 01EVERY CELL IS A NODE; ITS NEIGHBOURS ARE ITS EDGES — NO adj[] IS EVER BUILT
  • 02BOUNDS CHECK BEFORE THE VISITED CHECK, ALWAYS, OR YOU INDEX OUT OF RANGE
  • 03LEETCODE 200 IS 4-DIRECTIONAL; THE GfG VERSION USES ALL 8
  • 04THE OUTER DOUBLE LOOP OVER CELLS IS THE COMPONENT LOOP FROM UNIT 03
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
38 / VIDEO UNIT 07 · COMPONENTS IN A MATRIX

G-8 · Number of Islands | Components in Matrix

GRAPH SERIES
COMPONENTS IN A MATRIX
RUNTIME 25:04
AFTER THIS → 4 DRILLS · PROBLEM #07
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
39 / DRILL UNIT 07 · COMPONENTS IN A MATRIX

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

How many nodes and roughly how many edges does an m × n grid graph have?

One node per cell gives mn nodes. Each cell has up to 4 neighbours, so counting each edge once gives just under 2mn. That is why grid BFS is O(m×n) — V + E with both terms proportional to mn.

DRILL 02 · BUG

This neighbour check has a fatal ordering problem. Where?

int nr = r + dr[k], nc = c + dc[k];
if (!vis[nr][nc] && grid[nr][nc] == '1') {
  if (nr >= 0 && nr < m && nc >= 0 && nc < n) {
    // ... visit
  }
}

You read vis[nr][nc] before confirming nr and nc are in range. At the grid's edge that reads memory you do not own — sometimes a crash, often silently wrong, and it will pass the sample case. Bounds first, always.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
40 / DRILL UNIT 07 · COMPONENTS IN A MATRIX

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRACE

A 3×3 grid is 1 0 1 / 0 0 0 / 1 0 1. How many islands, 4-directionally?

Each corner 1 is isolated — diagonals do not connect in 4-directional movement, so nothing links them. Four islands. Switch to 8-directional and the same grid gives 4 still, since the centre is 0; but on a diagonal line of 1s the two conventions diverge sharply.

DRILL 02 · TRANSFER

Why does grid traversal usually skip building an explicit adj[] structure?

(r±1, c) and (r, c±1) gives you every neighbour arithmetically. Materialising an adjacency list would cost O(mn) extra memory to store what a two-line delta array already computes. The grid is its own adjacency structure.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
41 / PROBLEM #07 · COMPONENTS · MED

Number of Islands

MED components ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A grid of characters and a question about connected regions. No adjacency list is given and none is needed — the grid already encodes every edge.

INTUITION

Treat each cell as a node whose neighbours are the four cells around it. Then this is exactly component counting from unit 03, with the outer loop running over cells instead of nodes. Sinking each island as you find it makes the grid its own visited array.

STEPS
  1. Double loop over every cell
  2. On finding a '1', increment the count and start a traversal
  3. The traversal flips every reachable '1' to '0'
  4. Bounds-check before touching any cell
BRUTEO((mn)²)
OPTIMALO(m × n)
↕ SCROLL
int numIslands(vector<vector<char>>& grid) {
    int m = grid.size(), n = grid[0].size(), count = 0;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            if (grid[i][j] == '1') { sink(grid, i, j); count++; }

    return count;
}

void sink(vector<vector<char>>& g, int r, int c) {
    // bounds FIRST, then the value test
    if (r < 0 || r >= g.size() || c < 0 || c >= g[0].size()) return;
    if (g[r][c] != '1') return;

    g[r][c] = '0';              // repaint = visited
    sink(g, r+1, c);  sink(g, r-1, c);
    sink(g, r, c+1);  sink(g, r, c-1);
}
TIMEO(m × n)each cell visited a constant number of times
SPACEO(m × n)recursion depth, worst case the whole grid
TRAP

Bounds check before the grid access, never after. Writing if (!vis[nr][nc] && inRange(nr,nc)) reads out of bounds first — it often will not crash, it will just read garbage and pass your sample case.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #07 · NUMBER OF ISLANDS

G-8 · Number of Islands | Components in Matrix

The walkthrough for #07 Number of Islands. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-8 · Number of Islands | Components in Matrix
RUNTIME 25:04
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
42 / INTRO UNIT 08 · FLOOD FILL

UNIT 08 — FLOOD FILL

The paint-bucket tool, and it is DFS with one twist: the match condition is the starting colour, not a fixed value. Repainting doubles as the visited mark, which is why this one often needs no separate visited array at all.

THE QUESTION THIS LECTURE ANSWERS

HOW DOES A PAINT BUCKET TOOL ACTUALLY WORK?

FLOOD FILLSOURCE COLOURIN-PLACE MARKINGGUARD CLAUSE
WHAT TO WATCH FOR
  • 01THE MATCH IS AGAINST THE SOURCE CELL'S ORIGINAL COLOUR, CAPTURED BEFORE YOU START
  • 02REPAINTING IS THE VISITED MARK — NO SEPARATE vis[] NEEDED
  • 03IF newColor EQUALS oldColor YOU RECURSE FOREVER WITHOUT A GUARD
  • 04SPREAD IS 4-DIRECTIONAL, SAME DELTA ARRAY AS UNIT 07
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
43 / VIDEO UNIT 08 · FLOOD FILL

G-9 · Flood Fill Algorithm

GRAPH SERIES
FLOOD FILL
RUNTIME 20:34
AFTER THIS → 3 DRILLS · PROBLEM #08
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
44 / DRILL UNIT 08 · FLOOD FILL

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Why must you capture the starting colour into a variable before recursing?

The moment you paint the source, image[sr][sc] holds the new colour. Every later comparison against it would then match nothing, and the fill would stop after one cell. Read the old colour once, up front.

DRILL 02 · BUG

One input hangs this solution forever. Which?

If new equals old, repainting changes nothing, so the cell still matches on the next visit and you bounce between neighbours until the stack dies. The one-line fix is if (oldColor == newColor) return image; at the top. The repaint-as-visited-mark trick only works if the repaint is actually visible.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
45 / DRILL UNIT 08 · FLOOD FILL

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Flood Fill and Number of Islands are nearly the same code. What is the real difference?

Same traversal, different driver. Islands wraps it in the component loop and counts firings; Flood Fill is handed its source and fires once. Recognising that the inner traversal is identical is exactly the pattern-transfer these problems are testing.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
46 / PROBLEM #08 · GRID-DFS · MED

Flood Fill Algorithm

MED grid-dfs ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A starting cell plus a rule about spreading to same-valued neighbours. The match condition is relative to the source, not a fixed constant.

INTUITION

Identical traversal to Number of Islands, different driver: you are handed the source instead of searching for one, and you fire exactly once. Repainting doubles as the visited mark, so no separate array is needed.

STEPS
  1. Capture the source cell's original colour first
  2. If new colour equals old, return immediately — the guard
  3. DFS from the source, repainting every matching cell
  4. Stop at bounds or at any cell not matching the old colour
↕ SCROLL
vector<vector<int>> floodFill(vector<vector<int>>& img,
                              int sr, int sc, int color) {
    int old = img[sr][sc];
    if (old == color) return img;   // ← the guard
    fill(img, sr, sc, old, color);
    return img;
}

void fill(vector<vector<int>>& g, int r, int c,
          int old, int nw) {
    if (r < 0 || r >= g.size() || c < 0 || c >= g[0].size()) return;
    if (g[r][c] != old) return;

    g[r][c] = nw;
    fill(g,r+1,c,old,nw);  fill(g,r-1,c,old,nw);
    fill(g,r,c+1,old,nw);  fill(g,r,c-1,old,nw);
}
TIMEO(m × n)each cell repainted at most once
SPACEO(m × n)recursion depth
TRAP

If newColor == oldColor the repaint changes nothing, so every cell still matches on revisit and you recurse until the stack dies. One line at the top fixes it — and this is the case that hangs rather than fails loudly.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #08 · FLOOD FILL ALGORITHM

G-9 · Flood Fill Algorithm

The walkthrough for #08 Flood Fill Algorithm. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-9 · Flood Fill Algorithm
RUNTIME 20:34
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
47 / INTRO UNIT 09 · MULTI-SOURCE BFS

UNIT 09 — MULTI-SOURCE BFS

Everything rots at once, not one orange at a time. The trick is one line: push every source into the queue before the first pop. Then a BFS level is a unit of time, and the answer is just how many levels you ran.

THE QUESTION THIS LECTURE ANSWERS

WHAT IF THE SPREAD STARTS FROM MANY PLACES AT ONCE?

MULTI-SOURCE BFSLEVEL = TIMEFRESH COUNTSIMULTANEOUS SPREAD
WHAT TO WATCH FOR
  • 01PUSH ALL SOURCES BEFORE THE FIRST POP — THAT IS THE ENTIRE TECHNIQUE
  • 02ONE BFS LEVEL EQUALS ONE UNIT OF TIME, NOT ONE POP
  • 03COUNT FRESH ORANGES UP FRONT; IF ANY SURVIVE AT THE END, RETURN −1
  • 04AN EMPTY GRID OR ZERO FRESH ORANGES ANSWERS 0, NOT −1
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
48 / VIDEO UNIT 09 · MULTI-SOURCE BFS

G-10 · Rotten Oranges

GRAPH SERIES
MULTI-SOURCE BFS
RUNTIME 22:30
AFTER THIS → 4 DRILLS · PROBLEM #09
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
49 / DRILL UNIT 09 · MULTI-SOURCE BFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What makes a BFS 'multi-source'?

All sources go in at level 0, so they expand in lockstep. Running a separate BFS per source and taking minimums gives the same answer but costs O(sources × mn) instead of O(mn) — on a grid that is mostly rotten, that is the difference between passing and timing out.

DRILL 02 · TRACE

Queue holds 3 rotten oranges at time 0. After processing exactly those three and pushing their fresh neighbours, what time is it?

Time advances per level, not per pop. Those three were all level 0; draining them completes one round of spreading, so it is now minute 1. Incrementing time inside the pop loop instead of around it is the single most common wrong answer on this problem.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
50 / DRILL UNIT 09 · MULTI-SOURCE BFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution returns 4 on a grid where one fresh orange is walled off. What did it forget?

BFS happily terminates having reached everything it can reach. An unreachable fresh orange is invisible to it. You must count fresh up front, decrement on each rot, and check the counter is zero before returning the time — otherwise you answer for a subset of the grid.

DRILL 02 · TRANSFER

Which other sheet problem is the same multi-source technique?

01 Matrix is this exact shape: seed the queue with every zero cell, BFS outward, and each level is a distance. Seeing Rotten Oranges and 01 Matrix as one technique instead of two problems is the whole reason they sit next to each other on the sheet.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
51 / PROBLEM #09 · MULTI-SOURCE-BFS · MED

Rotten Oranges

MED multi-source-bfs ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Something spreads from several places simultaneously and you are asked for elapsed time. Minutes, rounds or steps until everything is covered means multi-source BFS.

INTUITION

Seed the queue with every rotten orange before the first pop, and they all expand in lockstep. One BFS level is one minute. Count fresh oranges up front so you can tell the difference between finished and stuck.

STEPS
  1. Scan the grid: push every 2 into the queue, count every 1
  2. While queue non-empty and fresh > 0, process a whole level at a time
  3. Rot each fresh neighbour, decrement the counter, push it
  4. Increment time after each full level; return −1 if fresh remains
BRUTEO((mn)²)
OPTIMALO(m × n)
↕ SCROLL
int orangesRotting(vector<vector<int>>& g) {
    int m = g.size(), n = g[0].size(), fresh = 0, time = 0;
    queue<pair<int,int>> q;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++) {
            if (g[i][j] == 2) q.push({i, j});  // ALL sources first
            if (g[i][j] == 1) fresh++;
        }

    int dr[] = {-1,1,0,0}, dc[] = {0,0,-1,1};
    while (!q.empty() && fresh) {
        int sz = q.size();            // one level = one minute
        while (sz--) {
            auto [r, c] = q.front();  q.pop();
            for (int d = 0; d < 4; d++) {
                int nr = r+dr[d], nc = c+dc[d];
                if (nr<0||nr>=m||nc<0||nc>=n||g[nr][nc]!=1) continue;
                g[nr][nc] = 2;  fresh--;  q.push({nr, nc});
            }
        }
        time++;
    }
    return fresh ? -1 : time;
}
TIMEO(m × n)each cell enters the queue at most once
SPACEO(m × n)queue holds up to mn cells
TRAP

Increment time per level, not per pop, and check for leftover fresh oranges at the end. A walled-off orange is invisible to BFS — without the fresh counter you confidently return a time for a grid that never fully rots.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #09 · ROTTEN ORANGES

G-10 · Rotten Oranges

The walkthrough for #09 Rotten Oranges. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-10 · Rotten Oranges
RUNTIME 22:30
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
52 / INTRO UNIT 10 · CYCLE DETECTION · UNDIRECTED

UNIT 10 — CYCLE DETECTION · UNDIRECTED

You are walking, you meet a node you have already seen — and it is not the one you just came from. That is a cycle. The parent check is the entire algorithm, and forgetting it makes every single edge look like a loop.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU KNOW A GRAPH LOOPS BACK ON ITSELF?

PARENTBACK EDGEUNDIRECTEDTREE EDGE
WHAT TO WATCH FOR
  • 01TRACK THE PARENT — WITHOUT IT, EVERY EDGE LOOKS LIKE A CYCLE
  • 02A VISITED NEIGHBOUR THAT IS NOT THE PARENT IS THE CYCLE CONDITION
  • 03CHECK EVERY COMPONENT — THE CYCLE MAY HIDE IN THE SECOND PIECE
  • 04THIS PARENT TRICK IS UNDIRECTED-ONLY; DIRECTED GRAPHS NEED PATH STATE
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
53 / VIDEO UNIT 10 · CYCLE DETECTION · UNDIRECTED

G-11 · Detect a Cycle in an Undirected Graph (BFS)

GRAPH SERIES
CYCLE DETECTION · UNDIRECTED
RUNTIME 20:19
AFTER THIS → 4 DRILLS · PROBLEM #10, #11
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
54 / DRILL UNIT 10 · CYCLE DETECTION · UNDIRECTED

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What is the exact condition that reports a cycle in an undirected graph?

In an undirected graph every edge is stored both ways, so from node v you always see your parent u sitting there visited. Treating that as a cycle reports one on a plain two-node edge. Visited AND not the parent is the real condition.

DRILL 02 · TRACE

Nodes 1–2–3 in a line, DFS from 1. At node 2 you look at neighbour 1, which is visited. Cycle?

1 is exactly where you came from. Skip it and carry on to 3. If you report a cycle here you would report one on every edge of every tree — which is the bug the parent parameter exists to prevent.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
55 / DRILL UNIT 10 · CYCLE DETECTION · UNDIRECTED

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A cycle checker returns false on a graph that clearly contains a cycle. What is most likely wrong?

Same trap as unit 03, wearing a different hat. One traversal reaches one component. If the cycle lives in a piece you never started from, you never see it. Every cycle-detection solution on this sheet is wrapped in the loop over all nodes.

DRILL 02 · TRANSFER

Why does this parent trick fail on a directed graph?

In a directed graph you can legitimately reach an already-finished node without any cycle — a diamond does this. What matters is whether the node is still on the current recursion path, which is why unit 15 introduces a second pathVis array. Same question, genuinely different machinery.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
56 / MECHANISM UNIT 10 · CYCLE DETECTION · UNDIRECTED · CODE MIRRORED

CYCLE DETECTION — VISITED, BUT NOT MY PARENT

Same canvas, same code mirror, new rule. Watch the parent get skipped harmlessly — then watch the edge that isn't a parent turn the whole thing red.

VISITED
QUEUE LIFO
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
57 / CONCEPT #10 · CYCLE-UNDIRECTED · HARD

Cycle Detection in Undirected Graph (BFS)

HARD cycle-undirected CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Does this graph contain a cycle? on an undirected graph. Also hiding inside is this a tree? — a tree is a connected acyclic graph.

INTUITION

Walk outward carrying each node's parent. Meeting a visited node is normal — it is usually just the parent you came from. Meeting a visited node that is not your parent means two different paths reached it, which is a cycle.

STEPS
  1. Queue holds {node, parent} pairs, not bare nodes
  2. Push the source with parent −1
  3. For each neighbour: unvisited → mark and push with this node as parent
  4. Visited and not the parent → return true
↕ SCROLL
bool bfsCycle(int src, vector<int> adj[], vector<int>& vis) {
    vis[src] = 1;
    queue<pair<int,int>> q;      // {node, parent}
    q.push({src, -1});

    while (!q.empty()) {
        auto [node, parent] = q.front();  q.pop();

        for (auto it : adj[node]) {
            if (!vis[it]) {
                vis[it] = 1;
                q.push({it, node});
            }
            else if (it != parent) return true;
        }
    }
    return false;
}

// caller: for (i) if (!vis[i] && bfsCycle(i,...)) return true;
TIMEO(V + E)a standard BFS over every component
SPACEO(V)queue of node-parent pairs
TRAP

Run it from every unvisited node, not just node 0. A cycle sitting in a second disconnected component is completely invisible to a single traversal, and this returns a confident false.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
58 / CONCEPT #11 · CYCLE-UNDIRECTED · HARD

Cycle Detection in Undirected Graph (DFS)

HARD cycle-undirected CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Same question as #10, and the recursive shape is usually shorter to write under time pressure. Reach for DFS unless V is large enough to threaten the call stack.

INTUITION

Identical rule — visited and not my parent — but the parent rides along as a function argument instead of inside a queue. The whole difference between #10 and #11 is where the parent is stored.

STEPS
  1. Pass parent as a parameter, −1 at the root
  2. Mark visited on entry
  3. Unvisited neighbour → recurse; if it returns true, propagate immediately
  4. Visited and not the parent → return true
↕ SCROLL
bool dfsCycle(int node, int parent,
              vector<int> adj[], vector<int>& vis) {
    vis[node] = 1;

    for (auto it : adj[node]) {
        if (!vis[it]) {
            // MUST propagate — not just call it
            if (dfsCycle(it, node, adj, vis)) return true;
        }
        else if (it != parent) {
            return true;
        }
    }
    return false;
}
TIMEO(V + E)same bound as the BFS version
SPACEO(V)recursion depth, up to V
TRAP

You must propagate the recursive result. Writing dfs(it, node, ...) and ignoring what it returns discards the discovery entirely and the function always answers false — a bug that reads as perfectly sensible code.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
59 / INTRO UNIT 11 · DISTANCE GRIDS

UNIT 11 — DISTANCE GRIDS

Distance to the nearest X, for every single cell. The naive read is one BFS per cell; the right read is one BFS from every X at once. Unit 09's technique, pointed at a different question.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND EVERY CELL'S DISTANCE TO THE NEAREST TARGET, ALL AT ONCE?

MULTI-SOURCEDISTANCE GRIDLEVEL = DISTANCEREVERSE BFS
WHAT TO WATCH FOR
  • 01SEED THE QUEUE WITH EVERY ZERO BEFORE THE FIRST POP
  • 02THE BFS LEVEL IS THE DISTANCE — NO SEPARATE MATHS NEEDED
  • 03ONE PASS ANSWERS FOR EVERY CELL, NOT JUST ONE
  • 04REVERSE THE QUESTION: BFS FROM THE TARGETS, NOT FROM THE ASKERS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
60 / VIDEO UNIT 11 · DISTANCE GRIDS

G-13 · Distance of Nearest Cell Having 1 | 0/1 Matrix

GRAPH SERIES
DISTANCE GRIDS
RUNTIME 20:21
AFTER THIS → 3 DRILLS · PROBLEM #12
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
61 / DRILL UNIT 11 · DISTANCE GRIDS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Reverse the obvious approach. What should you BFS *from*?

BFS from each 1 costs O((mn)²) — one traversal per cell. BFS from all zeros at once answers every cell in a single O(mn) pass, because the first time the wave reaches a cell is by definition its nearest zero. Reversing the direction of the search is the whole trick.

DRILL 02 · TRACE

A cell is first reached on the third BFS level. What is its answer?

Level is distance in an unweighted BFS. Third level means three steps from the nearest source, so the answer is 3 — and it does not matter which zero got there, because they all expanded in lockstep and the first arrival is necessarily the closest.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
62 / DRILL UNIT 11 · DISTANCE GRIDS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution initialises dist[][] to 0 everywhere and returns wrong answers. Why?

If everything starts at 0 you cannot tell distance zero from not yet reached, so the visited test never fires and cells get overwritten by later, longer paths. Use −1 or INT_MAX as the sentinel and only write a cell once.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
63 / PROBLEM #12 · MULTI-SOURCE-BFS · MED

Distance of Nearest Cell Having 1

MED multi-source-bfs ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Distance to the nearest 0 for every cell. Whenever a problem asks for a distance from all cells to a set of targets, reverse it and BFS outward from the targets.

INTUITION

One BFS per 1 is O((mn)²). Seed the queue with every 0 at once and a single pass answers the whole grid, because the first wave to reach a cell came from its nearest zero by construction.

STEPS
  1. Push every 0 into the queue; set its distance to 0
  2. Mark every 1 as unvisited with a −1 sentinel
  3. BFS outward; on reaching an unvisited cell write dist = dist[parent] + 1
  4. Never overwrite a cell that already has a distance
BRUTEO((mn)²)
OPTIMALO(m × n)
↕ SCROLL
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
    int m = mat.size(), n = mat[0].size();
    vector<vector<int>> dist(m, vector<int>(n, -1));
    queue<pair<int,int>> q;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            if (mat[i][j] == 0) { dist[i][j] = 0; q.push({i, j}); }

    int dr[] = {-1,1,0,0}, dc[] = {0,0,-1,1};
    while (!q.empty()) {
        auto [r, c] = q.front();  q.pop();
        for (int d = 0; d < 4; d++) {
            int nr = r+dr[d], nc = c+dc[d];
            if (nr<0||nr>=m||nc<0||nc>=n) continue;
            if (dist[nr][nc] != -1) continue;   // already answered
            dist[nr][nc] = dist[r][c] + 1;
            q.push({nr, nc});
        }
    }
    return dist;
}
TIMEO(m × n)each cell enters the queue exactly once
SPACEO(m × n)queue plus distance grid
TRAP

Initialise distances to −1, not 0. With 0 you cannot separate distance zero from not yet reached, the visited test never fires, and cells get overwritten by longer paths arriving later.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #12 · DISTANCE OF NEAREST CELL HAVING 1

G-13 · Distance of Nearest Cell Having 1 | 0/1 Matrix

The walkthrough for #12 Distance of Nearest Cell Having 1. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-13 · Distance of Nearest Cell Having 1 | 0/1 Matrix
RUNTIME 20:21
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
64 / INTRO UNIT 12 · BOUNDARY DFS

UNIT 12 — BOUNDARY DFS

Some regions are safe because they touch the border; everything else is trapped. The move is to invert the question — instead of hunting for surrounded regions, mark everything reachable from the boundary and let the rest fall out.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND THE REGIONS THAT DON'T TOUCH THE EDGE?

BOUNDARY DFSINVERSIONSAFE REGIONBORDER SEED
WHAT TO WATCH FOR
  • 01YOU CANNOT DECIDE A CELL IN ISOLATION — ITS WHOLE REGION DECIDES TOGETHER
  • 02START FROM THE BORDER AND MARK EVERYTHING REACHABLE AS SAFE
  • 03WHATEVER IS LEFT UNMARKED IS, BY DEFINITION, SURROUNDED
  • 04THE SAME BOUNDARY TRICK SOLVES ENCLAVES — ONLY THE FINAL STEP DIFFERS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
65 / VIDEO UNIT 12 · BOUNDARY DFS

G-14 · Surrounded Regions

GRAPH SERIES
BOUNDARY DFS
RUNTIME 23:17
AFTER THIS → 4 DRILLS · PROBLEM #13, #14
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
66 / DRILL UNIT 12 · BOUNDARY DFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Why start the traversal from the border rather than from each interior region?

Deciding is this region surrounded? from the inside means exploring the whole region and proving no cell escapes. Starting from the border computes the complement in one pass — mark the safe ones, and surrounded is simply everything you did not mark.

DRILL 02 · TRACE

A 4×4 grid of all O's. After the boundary pass, how many O's remain unmarked?

The four interior cells each touch a border cell, so the flood reaches everything. Zero get flipped. A region only counts as surrounded if no cell in it can walk to an edge — connectivity is what matters, not position.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
67 / DRILL UNIT 12 · BOUNDARY DFS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution flips cells to 'X' during the boundary traversal itself. What breaks?

The boundary traversal is finding the cells that must survive. Flipping them is exactly backwards. Mark them with a placeholder like '#', then in a final sweep turn every remaining 'O' into 'X' and every '#' back into 'O'.

DRILL 02 · TRANSFER

Number of Enclaves uses this identical boundary pass. What is the only real difference?

Same seed, same traversal, different final step: Surrounded Regions flips what it did not reach, Enclaves counts it. Two sheet problems, one technique — which is why the deck shows the second as a diff of the first.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
68 / PROBLEM #13 · BOUNDARY-DFS · MED

Surrounded Regions

MED boundary-dfs ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A region is safe only if it touches the boundary. Any problem phrased as surrounded, trapped or cannot escape is asking you to seed from the border.

INTUITION

Do not hunt for surrounded regions. Invert it: flood from every border 'O', mark everything you reach as safe, and whatever is still 'O' at the end was by definition unreachable from the edge.

STEPS
  1. Traverse from every 'O' on the four borders
  2. Mark each reached cell with a temporary '#'
  3. Sweep the grid: every remaining 'O' becomes 'X'
  4. Sweep again: every '#' goes back to 'O'
BRUTEO((mn)²)
OPTIMALO(m × n)
↕ SCROLL
void solve(vector<vector<char>>& b) {
    int m = b.size(), n = b[0].size();

    // 1. seed from the border only
    for (int i = 0; i < m; i++) {
        if (b[i][0]   == 'O') mark(b, i, 0);
        if (b[i][n-1] == 'O') mark(b, i, n-1);
    }
    for (int j = 0; j < n; j++) {
        if (b[0][j]   == 'O') mark(b, 0, j);
        if (b[m-1][j] == 'O') mark(b, m-1, j);
    }

    // 2. everything still 'O' was unreachable → flip it
    for (auto& row : b)
        for (auto& ch : row)
            ch = (ch == '#') ? 'O' : 'X';
}

void mark(vector<vector<char>>& b, int r, int c) {
    if (r<0||r>=b.size()||c<0||c>=b[0].size()||b[r][c]!='O') return;
    b[r][c] = '#';                 // safe, not flipped
    mark(b,r+1,c); mark(b,r-1,c); mark(b,r,c+1); mark(b,r,c-1);
}
TIMEO(m × n)border seed plus two linear sweeps
SPACEO(m × n)recursion depth
TRAP

Do not flip cells during the boundary traversal — that traversal is finding the ones that must survive. Mark them with a placeholder and flip the others afterwards, or you invert the answer perfectly.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #13 · SURROUNDED REGIONS

G-14 · Surrounded Regions

The walkthrough for #13 Surrounded Regions. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-14 · Surrounded Regions
RUNTIME 23:17
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
69 / PROBLEM #14 · BOUNDARY-DFS · MED

Number of Enclaves

MED boundary-dfs ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Land cells from which you cannot walk off the grid. Identical shape to #13 — seed from the boundary — with the last step swapped from flipping to counting.

INTUITION

Flood from every border land cell and sink everything reachable. Whatever land survives is enclaved, so the answer is simply the count of remaining 1s.

STEPS
  1. Traverse from every 1 on the four borders, sinking each to 0
  2. That removes all land connected to the edge
  3. Sweep the grid and count the 1s that are left
  4. Return the count
BRUTEO((mn)²)
OPTIMALO(m × n)
↕ SCROLL
// Δ FROM #13 — same boundary flood, different ending
int numEnclaves(vector<vector<int>>& g) {
    int m = g.size(), n = g[0].size();

    for (int i = 0; i < m; i++) {
        sink(g, i, 0);  sink(g, i, n-1);
    }
    for (int j = 0; j < n; j++) {
        sink(g, 0, j);  sink(g, m-1, j);
    }

    int count = 0;              // ← #13 flips here; we COUNT
    for (auto& row : g)
        for (int v : row) count += v;
    return count;
}

void sink(vector<vector<int>>& g, int r, int c) {
    if (r<0||r>=g.size()||c<0||c>=g[0].size()||g[r][c]!=1) return;
    g[r][c] = 0;
    sink(g,r+1,c); sink(g,r-1,c); sink(g,r,c+1); sink(g,r,c-1);
}
TIMEO(m × n)one boundary flood plus one counting sweep
SPACEO(m × n)recursion depth
TRAP

A single land cell sitting on the border is not an enclave, even though it looks isolated — it is already off the grid. Seed from every border cell, not just from border regions that look large.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #14 · NUMBER OF ENCLAVES

G-14 · Surrounded Regions

The walkthrough for #14 Number of Enclaves. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-14 · Surrounded Regions
RUNTIME 23:17
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
70 / INTRO UNIT 13 · SHAPE HASHING

UNIT 13 — SHAPE HASHING

Counting islands is unit 07. Counting distinct shapes needs one more idea: turn each island into a signature that is independent of where it sits on the grid, then drop the signatures into a set.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU TELL TWO ISLANDS APART BY SHAPE, NOT POSITION?

SHAPE SIGNATURENORMALISATIONBASE CELLSET DEDUPE
WHAT TO WATCH FOR
  • 01ABSOLUTE COORDINATES ARE USELESS — TWO IDENTICAL SHAPES HAVE DIFFERENT ONES
  • 02SUBTRACT THE BASE CELL FROM EVERY CELL TO NORMALISE
  • 03THE TRAVERSAL ORDER MUST BE FIXED, OR THE SAME SHAPE HASHES TWO WAYS
  • 04A set<vector<pair>> DOES THE DEDUPLICATION FOR YOU
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
71 / VIDEO UNIT 13 · SHAPE HASHING

G-16 · Number of Distinct Islands

GRAPH SERIES
SHAPE HASHING
RUNTIME 18:02
AFTER THIS → 3 DRILLS · PROBLEM #15
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
72 / DRILL UNIT 13 · SHAPE HASHING

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What makes two islands 'the same' in this problem?

Translation only — no rotation, no reflection. So the signature must survive a shift but nothing else, which is exactly what subtracting the base cell achieves. Equal cell counts is far too weak: an L-shape and a line of 3 both have three cells.

DRILL 02 · BUG

A solution stores absolute (row, col) pairs as the signature. What goes wrong?

An island at rows 0–1 and the same shape at rows 5–6 share no coordinates at all. Every island then looks unique and the answer equals the plain island count. Subtract the base cell from every coordinate so both collapse to the same relative form.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
73 / DRILL UNIT 13 · SHAPE HASHING

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Why must the traversal visit neighbours in a fixed order?

If you store the cells as an ordered vector, the order is part of the signature. Two identical shapes explored in different neighbour orders hash differently and get double-counted. Fix the direction order once and use it everywhere — or sort the signature before storing.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
74 / PROBLEM #15 · GRID-DFS · HARD

Number of Distinct Islands

HARD grid-dfs ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

The word distinct next to a shape-counting question. You are no longer counting regions, you are deduplicating them by form.

INTUITION

Traverse each island as usual, but record every cell's position relative to the island's first cell. That normalised list is a shape signature independent of position — drop them all in a set and take its size.

STEPS
  1. Loop cells; on unvisited land, start a traversal
  2. Record (r − baseR, c − baseC) for every cell reached
  3. Visit neighbours in a fixed direction order every time
  4. Insert the signature vector into a set; answer is set.size()
BRUTEO((mn)²)
OPTIMALO(m × n × log k)
↕ SCROLL
int countDistinctIslands(vector<vector<int>>& g) {
    int m = g.size(), n = g[0].size();
    vector<vector<int>> vis(m, vector<int>(n, 0));
    set<vector<pair<int,int>>> shapes;

    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            if (g[i][j] && !vis[i][j]) {
                vector<pair<int,int>> shape;
                dfs(g, vis, i, j, i, j, shape);   // pass the BASE cell
                shapes.insert(shape);
            }
    return shapes.size();
}

void dfs(vector<vector<int>>& g, vector<vector<int>>& vis,
         int r, int c, int br, int bc,
         vector<pair<int,int>>& shape) {
    if (r<0||r>=g.size()||c<0||c>=g[0].size()) return;
    if (!g[r][c] || vis[r][c]) return;

    vis[r][c] = 1;
    shape.push_back({r - br, c - bc});    // ← RELATIVE, not absolute

    dfs(g,vis,r+1,c,br,bc,shape);  dfs(g,vis,r-1,c,br,bc,shape);
    dfs(g,vis,r,c+1,br,bc,shape);  dfs(g,vis,r,c-1,br,bc,shape);
}
TIMEO(m × n × log k)one traversal plus set insertion per island
SPACEO(m × n)signatures stored for every island
TRAP

Storing absolute coordinates makes every island unique and the answer collapses to the plain island count. Subtract the base cell — and keep the neighbour order fixed, since the signature is a sequence and order is part of it.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #15 · NUMBER OF DISTINCT ISLANDS

G-16 · Number of Distinct Islands

The walkthrough for #15 Number of Distinct Islands. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-16 · Number of Distinct Islands
RUNTIME 18:02
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
75 / INTRO UNIT 14 · BIPARTITE / 2-COLOURING

UNIT 14 — BIPARTITE / 2-COLOURING

Can you split the nodes into two teams so that no edge stays inside a team? Colour as you traverse, always the opposite of where you came from. The only thing that can defeat you is an odd-length cycle.

THE QUESTION THIS LECTURE ANSWERS

CAN THIS GRAPH BE SPLIT INTO TWO GROUPS WITH NO EDGE INSIDE EITHER?

2-COLOURINGBIPARTITEODD CYCLECOLOUR ARRAY
WHAT TO WATCH FOR
  • 01COLOUR EVERY NEIGHBOUR THE OPPOSITE COLOUR — THAT IS THE WHOLE RULE
  • 02A NEIGHBOUR ALREADY WEARING YOUR COLOUR IS THE FAILURE CONDITION
  • 03ODD-LENGTH CYCLES ARE THE ONLY THING THAT MAKES IT IMPOSSIBLE
  • 04USE −1 FOR UNCOLOURED, NOT 0 — 0 IS A REAL COLOUR
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
76 / VIDEO UNIT 14 · BIPARTITE / 2-COLOURING

G-17 · Bipartite Graph (BFS)

GRAPH SERIES
BIPARTITE / 2-COLOURING
RUNTIME 18:29
AFTER THIS → 4 DRILLS · PROBLEM #16
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
77 / DRILL UNIT 14 · BIPARTITE / 2-COLOURING

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What exactly proves a graph is NOT bipartite?

Even cycles colour perfectly — alternate around and it closes cleanly. It is the odd cycle that fails, and it surfaces as an edge whose two ends already carry the same colour. Any cycle is far too broad a test.

DRILL 02 · TRACE

A triangle: nodes 1-2, 2-3, 3-1. Colour 1 as A. What happens?

A triangle is the smallest odd cycle. Walking round gives A, B, A — then the closing edge 3–1 puts two A's together. Any odd cycle forces this collision once you go all the way round, which is precisely why odd cycles and non-bipartiteness are the same statement.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
78 / DRILL UNIT 14 · BIPARTITE / 2-COLOURING

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution initialises the colour array to 0 and treats 0 as 'uncoloured'. What breaks?

0 is one of the two real colours. Using it as the sentinel means a node coloured 0 is indistinguishable from an untouched one — the clash test never fires and every graph reports bipartite. Use −1 for uncoloured.

DRILL 02 · TRANSFER

The graph is disconnected. What must you remember?

The same outer loop as always. A graph is bipartite only if every component is, so an odd cycle hiding in a component you never started from returns a confident, wrong 'true'.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
79 / MECHANISM UNIT 14 · BIPARTITE / 2-COLOURING · CODE MIRRORED

BIPARTITE — ALWAYS THE OPPOSITE COLOUR

Two sets, gold and ink. Every neighbour takes the opposite colour; a clash would mean an odd cycle.

VISITED
QUEUE FIFO
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
80 / PROBLEM #16 · COLOURING · HARD

Bipartite Graph

HARD colouring ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Two groups, two teams, no two adjacent items together. Any question about splitting nodes in half so that every edge crosses the divide.

INTUITION

Colour the source, then give every neighbour the opposite colour and keep going. If you ever meet a neighbour already wearing your own colour, the graph contains an odd cycle and no valid split exists.

STEPS
  1. Fill colour[] with −1 meaning uncoloured
  2. For every uncoloured node, start a BFS with colour 0
  3. Uncoloured neighbour → give it 1 − colour[node] and push
  4. Neighbour with the same colour → return false
↕ SCROLL
bool isBipartite(vector<vector<int>>& adj) {
    int n = adj.size();
    vector<int> color(n, -1);      // −1 = uncoloured

    for (int i = 0; i < n; i++) {
        if (color[i] != -1) continue;   // every component

        queue<int> q;
        color[i] = 0;  q.push(i);

        while (!q.empty()) {
            int node = q.front();  q.pop();
            for (int it : adj[node]) {
                if (color[it] == -1) {
                    color[it] = 1 - color[node];
                    q.push(it);
                }
                else if (color[it] == color[node]) return false;
            }
        }
    }
    return true;
}
TIMEO(V + E)a single BFS over every component
SPACEO(V)colour array plus queue
TRAP

Use −1 for uncoloured, never 0 — 0 is one of the two real colours, and using it as the sentinel means the clash test never fires and every graph reports bipartite. Then remember to run it on every component.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #16 · BIPARTITE GRAPH

G-17 · Bipartite Graph (BFS)

The walkthrough for #16 Bipartite Graph. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-17 · Bipartite Graph (BFS)
RUNTIME 18:29
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
81 / INTRO UNIT 15 · CYCLE DETECTION · DIRECTED

UNIT 15 — CYCLE DETECTION · DIRECTED

The parent trick from unit 10 does not work here. In a directed graph you can legitimately reach an already-visited node without any cycle. What matters is whether that node is still on the path you are currently walking.

THE QUESTION THIS LECTURE ANSWERS

WHY ISN'T THE PARENT CHECK ENOUGH ONCE EDGES HAVE DIRECTION?

pathVisRECURSION STACKBACK EDGEDIRECTED
WHAT TO WATCH FOR
  • 01TWO ARRAYS NOW: vis[] FOR EVER-SEEN, pathVis[] FOR ON THE CURRENT PATH
  • 02A CYCLE IS A NEIGHBOUR THAT IS VISITED *AND* STILL ON THE PATH
  • 03UNSET pathVis ON THE WAY OUT — THAT UNSET IS THE WHOLE ALGORITHM
  • 04A DIAMOND SHAPE REVISITS A NODE WITH NO CYCLE ANYWHERE
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
82 / VIDEO UNIT 15 · CYCLE DETECTION · DIRECTED

G-19 · Detect Cycle in a Directed Graph (DFS)

GRAPH SERIES
CYCLE DETECTION · DIRECTED
RUNTIME 17:22
AFTER THIS → 4 DRILLS · PROBLEM #17
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
83 / DRILL UNIT 15 · CYCLE DETECTION · DIRECTED

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What is the cycle condition in a directed graph?

Visited and still on the path. Plain 'visited' catches harmless revisits of finished branches; the parent check is meaningless once edges have direction. Only a node still sitting on your current path means you have walked in a circle.

DRILL 02 · TRACE

Edges 1→2, 1→3, 2→4, 3→4. DFS from 1 reaches 4 twice. Is there a cycle?

This is the diamond, and it is acyclic. When the 3→4 branch runs, node 4 is already finished and its pathVis is back to 0. This is exactly the case that makes a plain visited check report a cycle that does not exist.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
84 / DRILL UNIT 15 · CYCLE DETECTION · DIRECTED

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution sets pathVis[node] = 1 but never unsets it. What happens?

pathVis is meant to describe the current path only. Never unsetting it makes it a duplicate of vis[], and the diamond above immediately reports a false cycle. pathVis[node] = 0 just before returning is the line that makes the whole thing work.

DRILL 02 · TRANSFER

Which later problems rest on directed cycle detection?

A topological order exists if and only if the directed graph is acyclic, so Course Schedule is literally 'is there a cycle in the prerequisite graph?'. This unit is the foundation the entire topo section is built on.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
85 / CONCEPT #17 · CYCLE-DIRECTED · HARD

Cycle Detection in Directed Graph (DFS)

HARD cycle-directed CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Directed edges plus a cycle question — or anything about prerequisites, dependencies or build order, all of which are cycle questions wearing a costume.

INTUITION

Plain 'visited' is not enough: reaching a finished node is harmless. You need a second array saying this node is on the path I am walking right now. Visited AND on the current path means you have looped.

STEPS
  1. Keep two arrays: vis[] and pathVis[]
  2. On entry set both to 1
  3. Unvisited neighbour → recurse and propagate a true result
  4. Neighbour with pathVis == 1 → cycle; on exit reset pathVis to 0
↕ SCROLL
bool dfs(int node, vector<int> adj[],
         vector<int>& vis, vector<int>& pathVis) {

    vis[node] = 1;
    pathVis[node] = 1;          // now ON the current path

    for (auto it : adj[node]) {
        if (!vis[it]) {
            if (dfs(it, adj, vis, pathVis)) return true;
        }
        else if (pathVis[it]) {
            return true;      // visited AND still on path → cycle
        }
    }

    pathVis[node] = 0;          // ← leaving: off the path again
    return false;
}
TIMEO(V + E)each node and edge processed once
SPACEO(V)two arrays plus recursion depth
TRAP

Reset pathVis to 0 before returning. Leave it set and pathVis just duplicates vis — a plain diamond (1→2, 1→3, 2→4, 3→4) then reports a cycle that does not exist.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
86 / INTRO UNIT 16 · TOPOLOGICAL SORT (DFS)

UNIT 16 — TOPOLOGICAL SORT (DFS)

An ordering where every arrow points forwards. Do a DFS, and as each node finishes push it onto a stack — because a node only finishes after everything it depends on. Reverse the finish order and you have the answer.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU LINE UP TASKS SO EVERY PREREQUISITE COMES FIRST?

DAGTOPOLOGICAL ORDERFINISH TIMESTACK
WHAT TO WATCH FOR
  • 01PUSH ON THE WAY OUT, NOT ON THE WAY IN — FINISH ORDER IS THE KEY
  • 02THE STACK REVERSES IT FOR YOU; POPPING GIVES THE TOPOLOGICAL ORDER
  • 03ONLY DEFINED FOR A DAG — A CYCLE MEANS NO VALID ORDER EXISTS
  • 04THE ORDER IS NOT UNIQUE; ANY ARROW-RESPECTING ORDER IS CORRECT
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
87 / VIDEO UNIT 16 · TOPOLOGICAL SORT (DFS)

G-21 · Topological Sort Algorithm | DFS

GRAPH SERIES
TOPOLOGICAL SORT (DFS)
RUNTIME 13:30
AFTER THIS → 3 DRILLS · PROBLEM #18
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
88 / DRILL UNIT 16 · TOPOLOGICAL SORT (DFS)

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

In the DFS version, when is a node pushed onto the stack?

On the way out. A node finishes only once everything reachable from it has finished, so it belongs before all of them in the final order. The stack reverses the finish sequence, which turns that into exactly the topological order.

DRILL 02 · RECALL

Which graphs have a topological order at all?

A cycle means A waits on B which waits on A — no ordering can satisfy both. Topological order exists if and only if the graph is a DAG, which is why 'does a valid order exist?' and 'is there a cycle?' are the same question, and why Course Schedule is solvable this way.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
89 / DRILL UNIT 16 · TOPOLOGICAL SORT (DFS)

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRACE

Edges 1→2, 1→3, 3→2. Which of these is a valid topological order?

1 must precede both; 3 must precede 2. 1, 3, 2 satisfies every arrow. Note 1, 2, 3 would fail on the 3→2 edge — a common slip when people order by node number instead of by dependency.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
90 / CONCEPT #18 · TOPO · HARD

Topological Sort (DFS)

HARD topo CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

A directed acyclic graph plus any word about ordering, scheduling, dependencies or build sequence.

INTUITION

DFS, and push each node onto a stack as it finishes. A node finishes only after everything it points to has finished, so it belongs before all of them — and the stack hands that back reversed, which is the order.

STEPS
  1. Run DFS from every unvisited node
  2. Explore all outgoing edges first
  3. Push the node onto a stack on the way out
  4. Pop the whole stack — that is a topological order
↕ SCROLL
vector<int> topoSort(int V, vector<int> adj[]) {
    vector<int> vis(V, 0);
    stack<int> st;

    for (int i = 0; i < V; i++)
        if (!vis[i]) dfs(i, adj, vis, st);

    vector<int> order;
    while (!st.empty()) { order.push_back(st.top()); st.pop(); }
    return order;
}

void dfs(int node, vector<int> adj[],
         vector<int>& vis, stack<int>& st) {
    vis[node] = 1;
    for (auto it : adj[node])
        if (!vis[it]) dfs(it, adj, vis, st);

    st.push(node);        // ← ON THE WAY OUT
}
TIMEO(V + E)one DFS over the graph
SPACEO(V)stack plus recursion depth
TRAP

Push after the neighbour loop, not before. Pushing on entry gives discovery order, which is not a topological order and will pass a linear-chain test case while failing anything that branches.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
91 / INTRO UNIT 17 · KAHN'S ALGORITHM

UNIT 17 — KAHN'S ALGORITHM

The BFS answer to the same question, and often the more useful one. Count how many arrows point at each node; anything at zero has no unmet prerequisites and can go now. Remove it, decrement its targets, repeat.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU BUILD A TOPOLOGICAL ORDER WITHOUT RECURSION — AND DETECT CYCLES FOR FREE?

INDEGREEKAHN'S ALGORITHMDAGCYCLE VIA COUNT
WHAT TO WATCH FOR
  • 01INDEGREE IS THE COUNT OF ARROWS POINTING *AT* A NODE
  • 02SEED THE QUEUE WITH EVERY INDEGREE-0 NODE, NOT JUST ONE
  • 03PUSH A NODE ONLY WHEN ITS INDEGREE HITS EXACTLY ZERO
  • 04IF THE ORDER IS SHORTER THAN V, A CYCLE ATE THE REST — THAT IS THE CYCLE TEST
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
92 / VIDEO UNIT 17 · KAHN'S ALGORITHM

G-22 · Kahn's Algorithm | Topological Sort | BFS

GRAPH SERIES
KAHN'S ALGORITHM
RUNTIME 13:50
AFTER THIS → 4 DRILLS · PROBLEM #19, #20
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
93 / DRILL UNIT 17 · KAHN'S ALGORITHM

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Which nodes start in the queue?

Indegree 0 means nothing has to happen first, so those are exactly the tasks you can start immediately. Seeding only node 0 breaks the moment node 0 has a prerequisite, and there is usually more than one valid starting point.

DRILL 02 · RECALL

When exactly is a neighbour pushed?

Decrementing to 2 means two prerequisites remain — pushing then would place the task before its dependencies. Push only on hitting zero, which also guarantees each node is pushed exactly once.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
94 / DRILL UNIT 17 · KAHN'S ALGORITHM

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Kahn's finishes with only 5 of 8 nodes in the order. What does that mean?

Nodes on a cycle each wait on another node in that cycle, so their indegrees never fall to zero and they are never pushed. order.size() < V is a complete cycle test — no extra bookkeeping, which is why Kahn's is usually the cleaner answer for Course Schedule.

DRILL 02 · BUG

A solution builds indegrees by counting adj[i].size() for each i. What is wrong?

adj[i].size() is how many arrows leave i. Indegree is how many arrive, which means iterating every list and incrementing the target. Confusing the two inverts the algorithm and it produces a reverse-ish order that passes small tests.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
95 / MECHANISM UNIT 17 · KAHN'S ALGORITHM · CODE MIRRORED

KAHN'S — WATCH THE INDEGREES FALL

Same eight nodes, now directed low→high so the graph is a DAG. The row below is live indegree; gold means zero and ready.

VISITED
QUEUE FIFO
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
96 / CONCEPT #19 · TOPO · HARD

Kahn's Algorithm (BFS Topological Sort)

HARD topo CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Same DAG-ordering question as #18, but you also want cycle detection for free or you would rather avoid recursion depth entirely.

INTUITION

Count arrows arriving at each node. Anything at zero is ready now — emit it, remove its outgoing edges, and anything that drops to zero becomes ready. Peel the graph layer by layer.

STEPS
  1. Build indeg[] by iterating every adjacency list and incrementing targets
  2. Push every node with indegree 0
  3. Pop, append to the order, decrement each neighbour's indegree
  4. Push a neighbour the moment its indegree hits exactly 0
↕ SCROLL
vector<int> topoSort(int V, vector<int> adj[]) {
    vector<int> indeg(V, 0);
    for (int i = 0; i < V; i++)
        for (auto it : adj[i]) indeg[it]++;   // arrivals!

    queue<int> q;
    for (int i = 0; i < V; i++)
        if (indeg[i] == 0) q.push(i);

    vector<int> order;
    while (!q.empty()) {
        int node = q.front();  q.pop();
        order.push_back(node);

        for (auto it : adj[node])
            if (--indeg[it] == 0) q.push(it);   // only at zero
    }
    return order;   // order.size() < V → cycle
}
TIMEO(V + E)each node pushed once, each edge relaxed once
SPACEO(V)indegree array plus queue
TRAP

Indegree counts arrows arriving. Using adj[i].size() measures out-degree instead and inverts the whole algorithm — it still produces an order-shaped array, so it passes small symmetric tests.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
97 / CONCEPT #20 · TOPO · HARD

Detect a Cycle in a Directed Graph (Kahn's)

HARD topo CONCEPT · NO LEETCODE · DRILLS COVER IT
SIGNAL — WHAT GIVES IT AWAY

Detect a cycle in a directed graph, when you would rather not manage two recursion arrays. Kahn's gives it away as a byproduct.

INTUITION

Run Kahn's and count what comes out. Nodes on a cycle each wait on another node in that cycle, so their indegrees never fall to zero and they are never emitted. A short order means a cycle.

STEPS
  1. Run the standard Kahn's algorithm
  2. Count how many nodes were placed in the order
  3. count == V → acyclic
  4. count < V → the missing nodes lie on cycles
↕ SCROLL
// Δ FROM #19 — same Kahn's, one extra counter
bool isCyclic(int V, vector<int> adj[]) {
    vector<int> indeg(V, 0);
    for (int i = 0; i < V; i++)
        for (auto it : adj[i]) indeg[it]++;

    queue<int> q;
    for (int i = 0; i < V; i++)
        if (indeg[i] == 0) q.push(i);

    int placed = 0;
    while (!q.empty()) {
        int node = q.front();  q.pop();
        placed++;                       // ← count instead of collect
        for (auto it : adj[node])
            if (--indeg[it] == 0) q.push(it);
    }
    return placed != V;             // short → cycle
}
TIMEO(V + E)a single Kahn's pass
SPACEO(V)indegree array plus queue
TRAP

This is the directed test only. On an undirected graph every edge contributes indegree both ways and the count is meaningless — undirected cycles need the parent check from #10 and #11 instead.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
98 / INTRO UNIT 18 · COURSE SCHEDULE

UNIT 18 — COURSE SCHEDULE

Course Schedule is topological sort with the question rephrased. I asks whether an order exists; II asks for the order itself. Same Kahn's run — one returns a bool, the other returns the array.

THE QUESTION THIS LECTURE ANSWERS

CAN YOU FINISH ALL THE COURSES — AND IN WHAT ORDER?

PREREQUISITEDAGKAHN'SEMPTY ON CYCLE
WHAT TO WATCH FOR
  • 01prerequisites[i] = [a, b] MEANS b → a; READ THE DIRECTION CAREFULLY
  • 02COURSE SCHEDULE I IS JUST 'IS IT A DAG?'
  • 03COURSE SCHEDULE II RETURNS THE ORDER, OR AN EMPTY ARRAY ON A CYCLE
  • 04THE TWO SOLUTIONS DIFFER BY ABOUT TWO LINES
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
99 / VIDEO UNIT 18 · COURSE SCHEDULE

G-24 · Course Schedule I and II | Pre-requisite Tasks

GRAPH SERIES
COURSE SCHEDULE
RUNTIME 11:32
AFTER THIS → 3 DRILLS · PROBLEM #21, #22
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
100 / DRILL UNIT 18 · COURSE SCHEDULE

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

LeetCode gives prerequisites[i] = [a, b] meaning you must take b before a. Which edge?

b must come first, so the arrow runs b → a. Getting this backwards builds the reversed graph, which is still a DAG — so it does not crash, it just silently returns a reversed order and fails on the hidden tests. Read the direction twice.

DRILL 02 · TRANSFER

What is the actual difference between Course Schedule I and II?

One run, two return statements. I answers order.size() == V; II answers order, or an empty array when that check fails. Recognising them as one problem is worth more than solving both separately.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
101 / DRILL UNIT 18 · COURSE SCHEDULE

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

Course Schedule II returns a partial order on a cyclic input. What is missing?

Kahn's happily emits whatever it could place before stalling. Without the size check you hand back a partial ordering that looks plausible. Short order means cycle, and the contract says return empty.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
102 / PROBLEM #21 · TOPO-APPLY · HARD

Course Schedule I

HARD topo-apply ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Can you finish all the courses? Prerequisite pairs are directed edges, and the question is exactly is this graph acyclic?

INTUITION

Build the graph with an edge from prerequisite to course, run Kahn's, and check whether every course made it into the order. A valid schedule exists if and only if the graph is a DAG.

STEPS
  1. For each [a, b], add edge b → a (b must come first)
  2. Count indegrees and seed the queue with the zeros
  3. Run Kahn's, counting placed courses
  4. Return placed == numCourses
↕ SCROLL
bool canFinish(int n, vector<vector<int>>& pre) {
    vector<vector<int>> adj(n);
    vector<int> indeg(n, 0);

    for (auto& p : pre) {
        adj[p[1]].push_back(p[0]);   // b → a, NOT a → b
        indeg[p[0]]++;
    }

    queue<int> q;
    for (int i = 0; i < n; i++)
        if (indeg[i] == 0) q.push(i);

    int done = 0;
    while (!q.empty()) {
        int c = q.front();  q.pop();  done++;
        for (int nx : adj[c])
            if (--indeg[nx] == 0) q.push(nx);
    }
    return done == n;
}
TIMEO(V + E)one pass over courses and prerequisites
SPACEO(V + E)adjacency list plus indegree array
TRAP

prerequisites[i] = [a, b] means b before a, so the edge is b → a. Building a → b gives the reversed graph, which is still acyclic — so it does not crash, it just quietly answers the wrong question.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #21 · COURSE SCHEDULE I

G-24 · Course Schedule I and II | Pre-requisite Tasks

The walkthrough for #21 Course Schedule I. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-24 · Course Schedule I and II | Pre-requisite Tasks
RUNTIME 11:32
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
103 / PROBLEM #22 · TOPO-APPLY · MED

Course Schedule II

MED topo-apply ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Identical setup to #21, but the return type changes from bool to vector<int> — it wants the actual schedule.

INTUITION

The same Kahn's run. Collect the popped nodes instead of counting them, and if the collected order is shorter than numCourses a cycle made the schedule impossible, so return empty.

STEPS
  1. Build b → a edges and indegrees exactly as in #21
  2. Run Kahn's, appending each popped course to the order
  3. If order.size() != numCourses, return {}
  4. Otherwise return the order
↕ SCROLL
// Δ FROM #21 — collect instead of count, plus a size guard
vector<int> findOrder(int n, vector<vector<int>>& pre) {
    vector<vector<int>> adj(n);
    vector<int> indeg(n, 0);

    for (auto& p : pre) {
        adj[p[1]].push_back(p[0]);
        indeg[p[0]]++;
    }

    queue<int> q;
    for (int i = 0; i < n; i++)
        if (indeg[i] == 0) q.push(i);

    vector<int> order;              // ← was: int done = 0
    while (!q.empty()) {
        int c = q.front();  q.pop();
        order.push_back(c);            // ← was: done++
        for (int nx : adj[c])
            if (--indeg[nx] == 0) q.push(nx);
    }
    if (order.size() != n) return {};   // cycle → empty
    return order;
}
TIMEO(V + E)unchanged from Course Schedule I
SPACEO(V + E)adjacency plus order array
TRAP

Do not forget the final size check. Kahn's emits whatever it managed to place before stalling, so on a cyclic input you return a plausible-looking partial schedule instead of the empty array the contract demands.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #22 · COURSE SCHEDULE II

G-24 · Course Schedule I and II | Pre-requisite Tasks

The walkthrough for #22 Course Schedule II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-24 · Course Schedule I and II | Pre-requisite Tasks
RUNTIME 11:32
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
104 / INTRO UNIT 19 · EVENTUAL SAFE STATES

UNIT 19 — EVENTUAL SAFE STATES

A node is safe if every path out of it ends at a terminal — that is, if it can never wander into a cycle. The clean way to see it: reverse every edge and run Kahn's. Whatever gets placed is safe.

THE QUESTION THIS LECTURE ANSWERS

WHICH NODES CAN NEVER LEAD YOU INTO A CYCLE?

SAFE NODETERMINALREVERSE GRAPHKAHN'S
WHAT TO WATCH FOR
  • 01A TERMINAL NODE (OUTDEGREE 0) IS TRIVIALLY SAFE
  • 02REVERSE THE GRAPH SO TERMINALS BECOME INDEGREE-0 SEEDS
  • 03RUN KAHN'S ON THE REVERSED GRAPH — EVERYTHING PLACED IS SAFE
  • 04THE ANSWER MUST BE RETURNED IN SORTED ORDER
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
105 / VIDEO UNIT 19 · EVENTUAL SAFE STATES

G-25 · Find Eventual Safe States (BFS · Topo)

GRAPH SERIES
EVENTUAL SAFE STATES
RUNTIME 16:57
AFTER THIS → 3 DRILLS · PROBLEM #23
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
106 / DRILL UNIT 19 · EVENTUAL SAFE STATES

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

What makes a node safe?

Every path, not merely one. A node with two outgoing edges where one ends safely and the other enters a cycle is unsafe — you might take the bad branch. That universal quantifier is what makes the reverse-Kahn framing so clean.

DRILL 02 · RECALL

Why reverse the edges before running Kahn's?

Kahn's peels from indegree 0, but safety propagates backwards from terminals. Reversing turns 'outdegree 0' into 'indegree 0', so the standard algorithm now peels exactly the safe nodes, in the right direction, with no modification.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
107 / DRILL UNIT 19 · EVENTUAL SAFE STATES

CHECK — DID THAT LECTURE LAND?

DRILL 01 · BUG

A solution returns the correct safe nodes but fails the judge. Most likely cause?

Kahn's emits nodes in peel order, not numeric order. The problem explicitly asks for ascending output, so a final sort is required — a one-line omission that makes a correct algorithm fail every test.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
108 / PROBLEM #23 · TOPO-APPLY · HARD

Find Eventual Safe States

HARD topo-apply ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Safe, terminal, or never gets stuck in a loop. You are being asked which nodes have no path into a cycle.

INTUITION

Safety spreads backwards from terminals, and Kahn's peels forwards from indegree 0. Reverse every edge and the two line up: terminals become seeds, and everything Kahn's manages to place is safe.

STEPS
  1. Reverse every edge: for u → v, store v → u
  2. Indegree in the reversed graph equals outdegree in the original
  3. Run Kahn's; collect every node it places
  4. Sort ascending and return
BRUTEO(V × (V+E))
OPTIMALO(V + E log V)
↕ SCROLL
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
    int n = graph.size();
    vector<vector<int>> rev(n);
    vector<int> indeg(n, 0);

    for (int u = 0; u < n; u++)
        for (int v : graph[u]) {
            rev[v].push_back(u);       // REVERSE the edge
            indeg[u]++;                // = outdegree originally
        }

    queue<int> q;
    for (int i = 0; i < n; i++)
        if (indeg[i] == 0) q.push(i);   // terminals

    vector<int> safe;
    while (!q.empty()) {
        int node = q.front();  q.pop();
        safe.push_back(node);
        for (int nx : rev[node])
            if (--indeg[nx] == 0) q.push(nx);
    }
    sort(safe.begin(), safe.end());    // ← required
    return safe;
}
TIMEO(V + E log V)one Kahn's pass plus the final sort
SPACEO(V + E)the reversed adjacency list
TRAP

The judge wants the answer in ascending order, and Kahn's emits in peel order. Forgetting the final sort fails every test with a perfectly correct set of safe nodes.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #23 · FIND EVENTUAL SAFE STATES

G-25 · Find Eventual Safe States (BFS · Topo)

The walkthrough for #23 Find Eventual Safe States. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-25 · Find Eventual Safe States (BFS · Topo)
RUNTIME 16:57
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
109 / INTRO UNIT 20 · ALIEN DICTIONARY

UNIT 20 — ALIEN DICTIONARY

You are given words in a mystery alphabet's dictionary order and asked to recover the alphabet. The leap: compare adjacent words, find the first differing character, and that is one directed edge. Then topologically sort.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU RECOVER AN ALPHABET FROM A SORTED WORD LIST?

LEXICOGRAPHICFIRST DIFFERENCEIMPLIED EDGETOPO SORT
WHAT TO WATCH FOR
  • 01ONLY ADJACENT WORD PAIRS CARRY INFORMATION
  • 02THE FIRST DIFFERING CHARACTER GIVES EXACTLY ONE EDGE — THEN STOP COMPARING
  • 03PREFIX BEFORE SHORTER WORD (abc THEN ab) IS INVALID INPUT, NOT AN EDGE
  • 04A CYCLE MEANS NO CONSISTENT ALPHABET EXISTS
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
110 / VIDEO UNIT 20 · ALIEN DICTIONARY

G-26 · Alien Dictionary | Topological Sort

GRAPH SERIES
ALIEN DICTIONARY
RUNTIME 20:54
AFTER THIS → 3 DRILLS · PROBLEM #24
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
111 / DRILL UNIT 20 · ALIEN DICTIONARY

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Comparing 'wrt' and 'wrf', how many edges do you extract?

Sorted order only tells you about the first position where they differ; everything after it is unconstrained. So 'wrt' < 'wrf' yields t → f and nothing more. Extracting edges from later positions invents constraints that do not exist.

DRILL 02 · BUG

Input is ['abc', 'ab']. What must the code do?

In any lexicographic order the shorter prefix comes first. Seeing 'abc' before 'ab' means the input is inconsistent, and the answer is empty. This is the edge case that separates accepted from wrong answer — it produces no differing character, so a naive loop silently skips it.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
112 / DRILL UNIT 20 · ALIEN DICTIONARY

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

After building the edges, what does a cycle in the graph mean?

A cycle says x before y before … before x, which no alphabet can satisfy. Kahn's detects it the usual way: fewer letters placed than letters present. Same cycle test as Course Schedule, different dressing.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
113 / PROBLEM #24 · TOPO-APPLY · HARD

Alien Dictionary

HARD topo-apply ▶ SOLVE ON LEETCODE PREMIUM
SIGNAL — WHAT GIVES IT AWAY

Words given in the dictionary order of an unknown alphabet. The sortedness is the input; the alphabet is the output.

INTUITION

Adjacent words are the only source of information. The first position where two adjacent words differ gives exactly one ordering constraint — one directed edge. Collect them all, then topologically sort the letters.

STEPS
  1. For each adjacent pair, walk to the first differing character
  2. Add edge first→second for that position, then stop comparing this pair
  3. If no difference and the first word is longer, the input is invalid — return ""
  4. Run Kahn's over the letters; short order means a cycle, return ""
↕ SCROLL
string alienOrder(vector<string>& words) {
    int K = 26;
    vector<vector<int>> adj(K);
    vector<int> indeg(K, 0), seen(K, 0);

    for (auto& w : words)
        for (char ch : w) seen[ch - 'a'] = 1;

    for (int i = 0; i + 1 < words.size(); i++) {
        string& a = words[i];  string& b = words[i+1];
        int len = min(a.size(), b.size()), j = 0;

        while (j < len && a[j] == b[j]) j++;

        if (j == len) {
            if (a.size() > b.size()) return "";  // prefix violation
        } else {
            adj[a[j]-'a'].push_back(b[j]-'a');
            indeg[b[j]-'a']++;
        }
    }
    // then standard Kahn's over the 'seen' letters;
    // if fewer letters emerge than seen, a cycle exists → return ""
}
TIMEO(N × L + K)N words of length L, K distinct letters
SPACEO(K)graph over at most K letters
TRAP

['abc', 'ab'] is invalid input, not a no-op. A prefix must precede its extension, so this ordering is impossible and the answer is empty — but it produces no differing character, so a naive loop skips it and returns a confident wrong alphabet.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #24 · ALIEN DICTIONARY

G-26 · Alien Dictionary | Topological Sort

The walkthrough for #24 Alien Dictionary. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-26 · Alien Dictionary | Topological Sort
RUNTIME 20:54
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
114 / INTRO UNIT 21 · IMPLICIT GRAPHS

UNIT 21 — IMPLICIT GRAPHS

There is no graph in the input at all — just words. The unlock is realising that each word is a node and a one-letter change is an edge. Once you see that, 'shortest transformation' is plain BFS.

THE QUESTION THIS LECTURE ANSWERS

WHAT IF THE GRAPH ISN'T GIVEN — ONLY THE RULE FOR MOVING BETWEEN STATES?

IMPLICIT GRAPHSTATE SPACEWORD SET26 × L NEIGHBOURS
WHAT TO WATCH FOR
  • 01NODES ARE WORDS; EDGES ARE ONE-CHARACTER SUBSTITUTIONS
  • 02NEVER BUILD THE FULL GRAPH — GENERATE NEIGHBOURS ON DEMAND
  • 03USE A SET FOR O(1) DICTIONARY LOOKUP AND ERASE ON VISIT
  • 04BFS GIVES THE SHORTEST CHAIN BECAUSE EVERY STEP COSTS THE SAME
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
115 / VIDEO UNIT 21 · IMPLICIT GRAPHS

G-29 · Word Ladder I | Shortest Paths

GRAPH SERIES
IMPLICIT GRAPHS
RUNTIME 28:07
AFTER THIS → 3 DRILLS · PROBLEM #25
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
116 / DRILL UNIT 21 · IMPLICIT GRAPHS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

How are a word's neighbours generated?

Comparing against every word is O(N·L) per node. Generating 26 × L candidates and testing set membership is O(26·L) — independent of dictionary size. That swap is what makes the solution pass at large N.

DRILL 02 · RECALL

Why erase a word from the set the moment you enqueue it?

The set doubles as the visited structure. Skip the erase and a word gets reached from several predecessors at the same level, each pushing it again — the queue balloons and the search degenerates. Same 'mark on push' rule as unit 04, wearing different clothes.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
117 / DRILL UNIT 21 · IMPLICIT GRAPHS

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Why is plain BFS enough here, rather than Dijkstra?

Dijkstra exists for varying edge weights. When every step costs the same, BFS's level structure already yields shortest paths at lower cost. Reaching for Dijkstra on a unit-weight graph is a real interview tell.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
118 / PROBLEM #25 · IMPLICIT-GRAPH · HARD

Word Ladder I

HARD implicit-graph ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

A start state, an end state, a rule for legal moves, and the word shortest. No graph is given — you are expected to see one.

INTUITION

Each word is a node; two words are joined if they differ in exactly one letter. Every move costs the same, so BFS gives the shortest chain. Generate neighbours on demand rather than materialising the graph.

STEPS
  1. Put the word list in a hash set for O(1) lookup
  2. BFS from beginWord, tracking the level
  3. For each word, try all 26 letters at every position
  4. If the candidate is in the set, erase it and push it
BRUTEO(N² × L)
OPTIMALO(N × L × 26)
↕ SCROLL
int ladderLength(string begin, string end, vector<string>& list) {
    unordered_set<string> st(list.begin(), list.end());
    if (!st.count(end)) return 0;

    queue<string> q;
    q.push(begin);  st.erase(begin);
    int steps = 1;

    while (!q.empty()) {
        int sz = q.size();
        while (sz--) {
            string w = q.front();  q.pop();
            if (w == end) return steps;

            for (int i = 0; i < w.size(); i++) {
                char orig = w[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    w[i] = c;
                    if (st.count(w)) {
                        st.erase(w);      // visited mark
                        q.push(w);
                    }
                }
                w[i] = orig;
            }
        }
        steps++;
    }
    return 0;
}
TIMEO(N × L × 26)each word expanded once, 26·L candidates each
SPACEO(N × L)the set plus the queue
TRAP

Erase each word from the set as you enqueue it. Skip that and the same word is pushed by every neighbour that reaches it, the queue explodes, and a correct algorithm becomes a TLE.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #25 · WORD LADDER I

G-29 · Word Ladder I | Shortest Paths

The walkthrough for #25 Word Ladder I. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-29 · Word Ladder I | Shortest Paths
RUNTIME 28:07
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
119 / INTRO UNIT 22 · PATH RECONSTRUCTION

UNIT 22 — PATH RECONSTRUCTION

Word Ladder II wants every shortest chain, not merely the length. That changes the bookkeeping completely: you can no longer erase a word the instant you see it, because several equally-short paths may legitimately pass through it.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU RETURN EVERY SHORTEST PATH, NOT JUST ONE?

ALL SHORTEST PATHSPARENT MAPLEVEL-WISE ERASEBACKTRACK
WHAT TO WATCH FOR
  • 01ERASE PER LEVEL, NOT PER WORD — TWO PATHS MAY SHARE A NODE AT THE SAME DEPTH
  • 02RECORD EACH WORD'S PARENTS, THEN BACKTRACK FROM THE END
  • 03STOP AT THE FIRST LEVEL THAT REACHES THE TARGET — DEEPER PATHS ARE LONGER
  • 04THIS IS WHERE MOST BFS SOLUTIONS TURN INTO TLE
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
120 / VIDEO UNIT 22 · PATH RECONSTRUCTION

G-30 · Word Ladder II | Shortest Paths

GRAPH SERIES
PATH RECONSTRUCTION
RUNTIME 25:42
AFTER THIS → 3 DRILLS · PROBLEM #26
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
121 / DRILL UNIT 22 · PATH RECONSTRUCTION

CHECK — DID THAT LECTURE LAND?

DRILL 01 · RECALL

Why can't you erase a word from the set immediately, as in Word Ladder I?

I needs one path, so first-arrival-wins is fine. II needs all of them, and a word reachable from two predecessors at the same depth lies on two distinct shortest chains. Erase it on first sight and you silently drop half the answers. Delete only after the whole level finishes.

DRILL 02 · RECALL

How is the actual list of paths reconstructed?

Storing whole paths in the queue works but explodes in memory. Keeping a parent map and walking backwards from the target reconstructs every chain on demand — the same technique used to print a Dijkstra path.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
122 / DRILL UNIT 22 · PATH RECONSTRUCTION

CHECK — DID THAT LECTURE LAND?

DRILL 01 · TRANSFER

Why stop BFS at the first level where the end word appears?

BFS explores in strict distance order, so the first level containing the target holds every shortest chain. Continuing deeper can only find longer ones, which do not qualify — and costs the time that turns this solution into a TLE.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
123 / PROBLEM #26 · IMPLICIT-GRAPH · HARD

Word Ladder II

HARD implicit-graph ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Same transformation rule as #25, but it asks for all shortest sequences rather than the length. All shortest paths is a different problem.

INTUITION

Run the same BFS, but record every word's parents instead of erasing on sight — several shortest chains may share a node. Delete a level's words only once that whole level is done, then backtrack from the end.

STEPS
  1. BFS level by level, collecting words seen this level
  2. For each new word, append the current word to its parent list
  3. Erase the level's words from the set only after the level completes
  4. Stop at the first level containing endWord, then backtrack via parents
BRUTEexponential
OPTIMALO(N × L × 26 + P)
↕ SCROLL
// Δ FROM #25 — parents instead of a bare visited erase
vector<vector<string>> findLadders(string begin, string end,
                                    vector<string>& list) {
    unordered_set<string> st(list.begin(), list.end());
    unordered_map<string, vector<string>> parents;
    queue<string> q;  q.push(begin);
    bool found = false;

    while (!q.empty() && !found) {
        int sz = q.size();
        unordered_set<string> levelUsed;   // erase AFTER the level

        while (sz--) {
            string w = q.front();  q.pop();
            for (int i = 0; i < w.size(); i++) {
                string nx = w;  char orig = w[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    nx[i] = c;
                    if (nx == w || !st.count(nx)) continue;

                    if (!levelUsed.count(nx)) {
                        levelUsed.insert(nx);  q.push(nx);
                    }
                    parents[nx].push_back(w);   // ALL parents
                    if (nx == end) found = true;
                }
            }
        }
        for (auto& w : levelUsed) st.erase(w);
    }
    // backtrack from `end` through `parents` to emit every chain
}
TIMEO(N × L × 26 + P)BFS plus P output paths reconstructed
SPACEO(N × L + P)parent map plus the emitted paths
TRAP

Erasing per word instead of per level silently drops valid answers. A word reachable from two predecessors at the same depth lies on two distinct shortest chains; first-arrival-wins keeps only one, and the output looks plausible but is incomplete.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
SOLUTION #26 · WORD LADDER II

G-30 · Word Ladder II | Shortest Paths

The walkthrough for #26 Word Ladder II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-30 · Word Ladder II | Shortest Paths
RUNTIME 25:42
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
124 / TRAPS PLAUSIBLE, WRONG, AND SILENT

THE SIX THAT ACTUALLY BITE

Every one of these produces a plausible wrong answer rather than a crash. That is what makes them expensive — the judge tells you no, and the code looks fine.

TRAP 01 · VISITED ON POP

In BFS a node must be marked the moment it is pushed. Mark it on pop and the same node is enqueued once per incoming edge — the answers stay right while the queue grows toward O(E).

TRAP 02 · SKIPPING ALL VISITED

Undirected cycle detection skips only the parent, not every visited neighbour. Skip them all and you never detect a cycle at all; the function just returns false forever.

TRAP 03 · VISITED ≠ ON-STACK

A directed graph needs two marks: visited ever, and on the current recursion path. Using visited alone reports a cycle for any diamond shape, which is not a cycle.

TRAP 04 · BOUNDS AFTER VALUE

In grid problems check 0 <= r < m before reading grid[r][c]. The other order reads out of bounds — which in C++ usually does not crash, it just returns nonsense.

TRAP 05 · ONE SOURCE, NOT ALL

Multi-source BFS means pushing every source before the first pop. Push them one at a time inside the loop and you have computed distance from whichever source happened to go first.

TRAP 06 · SIZING BY V, NOT V+1

If the statement numbers nodes 1..V, declare adj[V+1]. Declaring adj[V] and touching node V writes off the end — silently, most of the time.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
125 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

This is the slide to reopen the night before — every traversal, its cost, and the sentence in the statement that selects it.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
BFS
O(V + E)
O(V)
Layer order, shortest path on unit weights.
DFS
O(V + E)
O(V)
Reachability, components, anything recursive.
Connected components
O(V + E)
O(V)
Outer loop firing a traversal on unvisited nodes.
Grid flood fill
O(m · n)
O(m · n)
Cells are nodes, 4 or 8 moves are edges.
Multi-source BFS
O(V + E)
O(V)
Push every source first, then run once.
Cycle · undirected
O(V + E)
O(V)
DFS skipping the parent only.
Cycle · directed
O(V + E)
O(V)
DFS with an on-recursion-stack mark.
Topological sort
O(V + E)
O(V)
Prerequisites, ordering, Kahn's indegree queue.
Bipartite check
O(V + E)
O(V)
Two-colour; a conflict means an odd cycle.
INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
126 / CLOSE STEP 15 · DECK 1 OF 3

GRAPH TRAVERSAL — DONE

Twenty-two units, 26 sheet problems, and underneath them two traversals. If you remember one thing: decide whether the question is about layers or about reachability, and the algorithm picks itself.

00%
OF THIS DECK SOLVED
DECK 2 · SHORTEST PATHS →ALL TOPICS

Decks 2 covers Dijkstra, Bellman-Ford and Floyd-Warshall. Deck 3 — MST, disjoint set and strongly connected components — is not built yet.

INVARIANT · GRAPHS · TRAVERSAL · DECK 1 OF 3
INVARIANT · STEP 15 · DECK 1 OF 3

Open this on a laptop

WATCH IT RUN · THEN RUN IT FROM MEMORY

This deck is a fixed 1280 × 720 stage — hand-built animated visualisers, code mirrored beside the graph, problems laid out two columns wide. It scales as one piece, so on this screen the text would land at about 4px. That is not worth shipping to you.

NEEDS ≥ 1060 × 610 CSS PX THIS SCREEN — 0 × 0

Your progress is saved on this device, so anything you tick on the laptop will be here too.