INVARIANT · GRAPHS · MST · DSU · SCC
01
00/13
01 / COVER STEP 15 · GRAPHS
INVARIANT · STEP 15 · DECK 3 OF 3
SPANNING SETS

One structure underneath almost all of it: which things are already connected to which. Answer that in near-constant time and minimum spanning trees, island counting and account merging all collapse into the same four lines.

13Problems
13Units
13Lectures
13Visualisers
← → ↑ ↓  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 · MST · DSU · SCC · DECK 3 OF 3
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

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

ASSUMEDA priority queue for Prim (unit 02). Heaps is step 11 and not built yet; the Disjoint Set everything else leans on is taught here in unit 03.

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.

13 PROBLEMS · 8 LINK TO A JUDGE · THE REST ARE CONCEPTS THE DRILLS COVER

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 13 PROBLEMS

Click any problem to jump straight to it. Solved ones turn green. Press I from anywhere to come back here. The number is the sheet's row, not your position — the deck teaches in the order that builds, so you will meet them slightly out of numerical sequence.

MINIMUM SPANNING TREE · 04
DISJOINT SET AND PROBLEMS · 06
OTHER ALGORITHMS · 03
SOLVED HAS A JUDGE LINK CONCEPT — DRILLS ONLY
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

WHICH TOOL, AND WHY

Deck 2 asked when a distance may be called final. This deck asks a smaller question over and over — are these two things already connected? — and almost everything here is an answer to it.

CONNECT EVERYTHING, CHEAPEST

Every node must end up connected and no source or destination is named.

MST · PRIM OR KRUSKALO(E log E)
GRAPH ARRIVES AS AN EDGE LIST

You are handed {u, v, w} triples, or the graph converts to them cheaply.

KRUSKALO(E log E)
GRAPH ARRIVES AS AN ADJACENCY LIST

Neighbours per node, and reshaping to an edge list would be wasted work.

PRIMO(E log V)
ARE THESE TWO CONNECTED YET?

Merges happen over time and you only ever query membership, never the route.

DISJOINT SETO(α(n))
COUNT THE GROUPS AS THEY MERGE

Components after each operation, islands appearing one at a time.

DSU + A COUNTERO(q · α(n))
MINIMISE THE WORST STEP ON A PATH

The maximum cell, the highest water level, the smallest sufficient ceiling.

ACTIVATE IN ORDER + DSUO(n² α)
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
05 / CONSTRAINTS BEFORE YOU WRITE A LINE

READ THE ANSWER OFF THE CONSTRAINTS

Two questions decide everything here. Is the graph dense or sparse — that picks Prim's form or Kruskal. And are the connections arriving over time — that is the signal for Disjoint Set rather than a fresh traversal per query.

n ≤
BUDGET
WHAT THAT BUYS YOU
N ≤ 20
O(2^N · N)
bitmask over subsets of nodes — not what any tool here does
N ≤ 1000
O(N²)
Prim on a dense matrix, no heap required
E ≤ 10⁵
O(E log E)
Kruskal — the sort dominates, then near-constant DSU checks
N, E ≤ 10⁵
O(E log N)
Prim with a priority queue. THE home row for a sparse MST
q queries ≤ 10⁵
O(q · α)
Disjoint Set — near-constant per union or find, which is why it beats a re-run DFS

SPARSE ⇒ KRUSKAL / PRIM+HEAP · QUERIES OVER TIME ⇒ DISJOINT SET

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
06 / ROADMAP WHAT YOU ARE ABOUT TO DO

THE WHOLE RUN, 13 UNITS

Thirteen units in three movements. Units 01–04 build the MST and the Disjoint Set it leans on; 05–10 are six problems that are all the same connectivity query in disguise; 11–13 change subject to strongly connected components, bridges and articulation points.

UNIT 01

Minimum Spanning Tree — Theory

▶ 7:593 DRILLS1 PROBLEM
UNIT 02

Prim's Algorithm

▶ 19:103 DRILLS1 PROBLEM
UNIT 03

Disjoint Set — Union by Rank, by Size, Path Compression

▶ 42:154 DRILLS1 PROBLEM
UNIT 04

Kruskal's Algorithm

▶ 13:113 DRILLS2 PROBLEMS
UNIT 05

Number of Provinces — with Disjoint Set

▶ 8:033 DRILLSNO SHEET ROW
UNIT 06

Number of Operations to Make Network Connected

▶ 14:483 DRILLS1 PROBLEM
UNIT 07

Accounts Merge

▶ 22:013 DRILLS1 PROBLEM
UNIT 08

Number of Islands II — Online Queries

▶ 25:323 DRILLS1 PROBLEM
UNIT 09

Making a Large Island

▶ 26:163 DRILLS1 PROBLEM
UNIT 10

Most Stones Removed with Same Row or Column

▶ 23:513 DRILLS1 PROBLEM
UNIT 11

Strongly Connected Components — Kosaraju

▶ 22:443 DRILLS1 PROBLEM
UNIT 12

Bridges in a Graph — Tarjan

▶ 23:253 DRILLS1 PROBLEM
UNIT 13

Articulation Points

▶ 22:003 DRILLS1 PROBLEM
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
07 / WARMUP LOAD THE SPANNING-TREE FACTS FIRST

TWO THINGS WORTH HAVING IN MEMORY

DRILL 01 · RECALL

A connected undirected graph has N nodes. How many edges does any spanning tree of it contain, and why is the count fixed?

Exactly N − 1. Touch all N nodes with fewer and the graph is disconnected; use more and you have closed a cycle. Every algorithm in this deck is therefore the same shape: take edges until you hold N − 1 of them, refusing any that would close a cycle. Prim and Kruskal differ only in the order they consider edges — and the count gives you a free termination test.

DRILL 02 · RECALL

You repeatedly need to answer ‘are these two nodes already in the same group?’ as groups keep merging. What does a plain DFS per query cost you?

A whole O(N + E) sweep, every single time. With q queries that is O(q·(N + E)), and the work is almost entirely repeated — the connectivity you computed last query is still true. Disjoint Set exists to keep that answer between queries, giving near-constant time per question. This is why unit 03 comes before the six problems that follow it: they are all the same query asked in a costume.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
08 / INTRO UNIT 01 · Minimum Spanning Tree — Theory

UNIT 01 — Minimum Spanning Tree — Theory

A spanning tree is any set of edges that touches every node and closes no cycle — which pins it at exactly n − 1 edges. The minimum one is whichever of those has the smallest total weight. That is the entire definition, and getting it exactly right is what stops the next four units from feeling arbitrary.

THE QUESTION THIS LECTURE ANSWERS

WHAT EXACTLY MAKES A SET OF EDGES A MINIMUM SPANNING TREE?

SPANNING TREEMSTCYCLEn−1 EDGESTOTAL WEIGHT
WHAT TO WATCH FOR
  • 01n NODES MEANS EXACTLY n−1 EDGES — MORE IS A CYCLE, FEWER IS DISCONNECTED
  • 02SPANNING MEANS EVERY NODE IS TOUCHED, NOT THAT EVERY EDGE IS USED
  • 03MINIMUM IS ABOUT TOTAL WEIGHT — NEVER ABOUT THE FEWEST EDGES
  • 04AN MST NEED NOT BE UNIQUE: EQUAL WEIGHTS ALLOW SEVERAL, ALL VALID
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
09 / VIDEO UNIT 01 · Minimum Spanning Tree — Theory

G-44. Minimum Spanning Tree - Theory

GRAPH SERIES
Minimum Spanning Tree — Theory
RUNTIME 7:59
AFTER THIS → 3 DRILLS · PROBLEM #01
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
10 / DRILL UNIT 01 · Minimum Spanning Tree — Theory · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

A connected graph has n nodes. How many edges does any spanning tree of it have?

Exactly n − 1, always, whatever the weights are. Add one more edge to a tree and you necessarily close a cycle; remove one and the graph falls into two pieces. That is why taken == n - 1 is a legitimate early exit in Kruskal — the count alone tells you that you are finished.

DRILL 02 · RECALL

Two different edge sets of a graph both total 24, and 24 is the smallest total achievable. Which is the MST?

Uniqueness is not part of the definition. The lecture is careful about this: the moment two edges share a weight, several equally minimal trees can exist and every one is a correct answer. This is exactly why judge problems ask for the MST's weight and almost never for the edge list — the weight is unique even when the tree is not.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
11 / DRILL UNIT 01 · Minimum Spanning Tree — Theory · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You built the MST of a weighted graph. Is the path it gives between two nodes also the shortest path between them?

The single most expensive confusion available right now, because you have just spent a whole deck on shortest paths. They optimise different things: Dijkstra minimises one path from a source; an MST minimises the sum over the whole edge set. An MST will happily route you the long way round between two nodes if that saves weight elsewhere in the graph.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
12 / MECHANISM UNIT 01 · Minimum Spanning Tree — Theory · CODE MIRRORED

ONE THEOREM, AND BOTH ALGORITHMS ARE IT

Split the nodes into any two groups — any split at all. The edges with one end in each group cross the cut, and the cheapest of them is always safe: every spanning tree has to cross somewhere, and a tree that crossed only by a dearer edge could swap it for this one and get cheaper. That is the whole justification. Prim keeps one group and grows it; Kruskal takes the cheapest safe edge anywhere — same theorem, two schedules.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
13 / CONCEPT #01 · MST · EASY

Minimum Spanning Tree — Theory

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

The statement wants every node connected at the lowest total cost, and names no source and no destination. No source means no shortest path — it is a spanning problem.

INTUITION

Two conditions, both required: touch all n nodes, and use exactly n − 1 edges. The edge count is what forces acyclicity, and connectivity is what forces it to span. Verify both and you have a spanning tree; among all of those, the cheapest is the MST.

STEPS
  1. Count the edges — anything other than n − 1 is disqualified immediately
  2. Build an adjacency list from just the candidate edges
  3. Traverse from any one node and count how many you reach
  4. It spans if and only if you reached all n
↕ SCROLL
// A spanning tree is exactly two things: n-1 edges, and all n nodes
// reachable. Either check alone passes graphs that are not spanning trees.
bool isSpanningTree(int n, vector<pair<int,int>>& tree) {
    if ((int)tree.size() != n - 1) return false;   // more => a cycle
                                                  // fewer => disconnected
    vector<vector<int>> adj(n);
    for (auto &[u, v] : tree) {
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    vector<bool> seen(n, false);
    queue<int> q;
    q.push(0);
    seen[0] = true;
    int reached = 1;

    while (!q.empty()) {
        int v = q.front(); q.pop();
        for (int nb : adj[v])
            if (!seen[nb]) {
                seen[nb] = true;
                reached++;
                q.push(nb);
            }
    }
    return reached == n;          // SPANNING means every node, not most of them
}
TIMEO(V + E)one traversal over the candidate edge set
SPACEO(V + E)the adjacency list plus the seen array
TRAP

Checking only the edge count is the trap that looks like diligence. A graph can have exactly n − 1 edges and still be wrong — a triangle plus a detached node has 3 edges for 4 nodes, satisfying the count while being both cyclic and disconnected. Both conditions, always.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
14 / INTRO UNIT 02 · Prim's Algorithm

UNIT 02 — Prim's Algorithm

Prim's grows one single tree outward from an arbitrary start. At every step it takes the cheapest edge that leaves the tree and reaches somewhere new. No sorting and no disjoint set — a priority queue of frontier edges is the whole apparatus.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU GROW A MINIMUM SPANNING TREE OUTWARD FROM A SINGLE NODE?

PRIMCUT PROPERTYPRIORITY QUEUEinMSTFRONTIER EDGE
WHAT TO WATCH FOR
  • 01THE PQ HOLDS EDGE WEIGHTS INTO THE TREE — NOT DISTANCES FROM A SOURCE
  • 02inMST IS SET ON POP, NEVER ON PUSH — A NODE IS QUEUED ONCE PER INCOMING EDGE
  • 03total += w ADDS THE EDGE'S OWN COST, NOT AN ACCUMULATED PATH COST
  • 04THE CODE IS DIJKSTRA WITH ONE LINE CHANGED, AND THAT LINE IS THE ALGORITHM
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
15 / VIDEO UNIT 02 · Prim's Algorithm

G-45. Prim's Algorithm - Minimum Spanning Tree

GRAPH SERIES
Prim's Algorithm
RUNTIME 19:10
AFTER THIS → 3 DRILLS · PROBLEM #02
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
16 / DRILL UNIT 02 · Prim's Algorithm · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

At what moment does Prim's commit a node to the MST for good?

On the pop, guarded by if (inMST[v]) continue;. A node gets pushed once for every edge that reaches it, at several different weights. The priority queue guarantees the cheapest of those copies surfaces first; every later copy is stale and must be discarded. Marking on push instead would commit whichever edge happened to be seen first.

DRILL 02 · BUG

Someone adapts their Dijkstra into Prim's but leaves the push line untouched: pq.push({dist[v] + nw, nb}). What have they built?

This is the difference between the two algorithms, and it is one term. Prim's pushes nw — the edge's own weight, because it only cares what it costs to attach that node to the tree. Dijkstra pushes dist[v] + nw, the accumulated cost from the source. Both run, both terminate, both return a plausible number, and only one of them is an MST — which is what makes it expensive.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
17 / DRILL UNIT 02 · Prim's Algorithm · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRACE

Step the visualiser on the previous slide to the end. What total does Prim's produce on this deck's graph?

24 — and starting from a different node does not change it. The start node decides the order edges get taken and can even change which edges are taken when weights tie, but the minimum total is a property of the graph. Kruskal reaches the same 24 by a completely different route, which is the point of running both on one picture.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
18 / MECHANISM UNIT 02 · Prim's Algorithm · CODE MIRRORED

PRIM — GROW ONE TREE OUTWARD

No sorting and no DSU: keep one growing tree and always take the cheapest edge leaving it. Edges get considered in a different order than Kruskal uses — and the total comes out identical at 24.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
19 / CONCEPT #02 · MST · HARD

Prim's Algorithm

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

You are asked for the weight of a minimum spanning tree and handed an adjacency list. When the graph arrives by neighbours rather than as a flat edge list, Prim's fits the input without reshaping it.

INTUITION

Keep one growing tree. The priority queue holds every edge currently leaving it, keyed by that edge's own weight. Pop the cheapest, and if it reaches somewhere new, absorb that node and push its outgoing edges. Stale entries — edges to nodes already absorbed — are discarded on the way out.

STEPS
  1. Push (0, start) so the first pop costs nothing
  2. Pop the cheapest (w, v); if v is already in the tree, discard and continue
  3. Mark v as in-tree and add w to the running total
  4. Push (weight, neighbour) for every neighbour not yet in the tree
  5. The queue empties exactly when the tree spans the graph
BRUTEO(V²)
OPTIMALO(E log V)
↕ SCROLL
int spanningTree(int V, vector<vector<int>> adj[]) {
    // {weight, node} — the weight is the EDGE's cost to attach `node`,
    // NOT an accumulated distance from the start. That is the whole
    // difference between this and Dijkstra.
    priority_queue<pair<int,int>, vector<pair<int,int>>,
                   greater<pair<int,int>>> pq;
    vector<bool> inMST(V, false);
    int total = 0;

    pq.push({0, 0});                  // any start node; first edge is free

    while (!pq.empty()) {
        auto [w, v] = pq.top(); pq.pop();
        if (inMST[v]) continue;       // a stale, more expensive copy — drop it
        inMST[v] = true;              // FINAL only here, on the pop
        total += w;
        for (auto &e : adj[v]) {
            int nb = e[0], nw = e[1];
            if (!inMST[nb]) pq.push({nw, nb});   // nw, not total + nw
        }
    }
    return total;
}
TIMEO(E log V)each edge is pushed once and popped once, log V per operation
SPACEO(V + E)the queue holds at most one entry per edge
TRAP

Marking inMST[v] at push time instead of at pop is the bug that survives every small test. The node gets committed via whichever edge reached it first rather than the cheapest, so the total comes out too large — a plausible number, silently wrong, on a graph big enough to matter.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
20 / INTRO UNIT 03 · Disjoint Set — Union by Rank, by Size, Path Compression

UNIT 03 — Disjoint Set — Union by Rank, by Size, Path Compression

Disjoint Set answers one question — are these two already in the same group? — and performs one action: merge two groups. Both in effectively constant time. It is the most reused structure in this deck: every remaining unit except the last three is an application of it.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU ANSWER 'ARE THESE TWO ALREADY CONNECTED?' IN NEAR-CONSTANT TIME?

DSUFINDUNIONPATH COMPRESSIONUNION BY SIZEα(n)
WHAT TO WATCH FOR
  • 01UNION BY SIZE KEEPS THE TREE SHALLOW; PATH COMPRESSION FLATTENS WHAT SURVIVES
  • 02find() IS NOT READ-ONLY — IT REWRITES THE PARENT OF EVERY NODE ON THE PATH
  • 03ALWAYS UNITE THE TWO ROOTS, NEVER THE TWO NODES YOU WERE HANDED
  • 04α(n) IS BELOW 5 FOR ANY n YOU WILL EVER RUN — TREAT IT AS CONSTANT
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
21 / VIDEO UNIT 03 · Disjoint Set — Union by Rank, by Size, Path Compression

G-46. Disjoint Set | Union by Rank | Union by Size | Path Compression

GRAPH SERIES
Disjoint Set — Union by Rank, by Size, Path Compression
RUNTIME 42:15
AFTER THIS → 4 DRILLS · PROBLEM #03
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
22 / DRILL UNIT 03 · Disjoint Set — Union by Rank, by Size, Path Compression · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

In union by size, which root ends up as the parent?

The larger tree keeps its root, and the smaller one is hung beneath it. Do it the other way and every node in the big tree gets one level deeper — you would be paying the cost on the many to save it on the few. Hanging small under large means only the smaller group's nodes descend, which caps any node's depth at log n even before compression runs.

DRILL 02 · BUG

A student writes void unite(int a, int b) { par[b] = a; sz[a] += sz[b]; } with no find() calls. What breaks?

The single most common DSU bug there is. b may be deep inside its tree, so overwriting par[b] tears its existing subtree away from its real root and re-hangs it elsewhere. Nodes that were connected quietly stop being connected, and the sizes drift from reality. Resolve both to roots firsta = find(a); b = find(b); — then link. It never crashes; it just returns wrong answers.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
23 / DRILL UNIT 03 · Disjoint Set — Union by Rank, by Size, Path Compression · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why is find() written to assign — return par[x] = find(par[x]); — rather than just returning the root it found?

That assignment is path compression, and it is half of why the structure is fast. Walking from a deep node to its root is expensive once; the assignment makes sure you never pay it again, because every node touched on the way now points straight at the root. A find that only reads is correct but leaves the tree exactly as tall as it was, and you drop from α(n) back to log n.

DRILL 02 · TRANSFER

You need to report the number of connected components after each of q union operations. What is the cheapest way?

Start at n components and subtract one on every successful merge — which is precisely why unite is written to return a bool rather than void. A union whose two nodes already share a root changes nothing and must not decrement. This turns an O(q · (V+E)) loop into O(q · α(n)), and it is the shape of the next four problems in this deck.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
24 / MECHANISM UNIT 03 · Disjoint Set — Union by Rank, by Size, Path Compression · CODE MIRRORED

DISJOINT SET — UNION BY SIZE, PATH COMPRESSION

Watch the forest rearrange. A union hangs the smaller tree under the larger one, and then find(8) compresses its whole path — every node on it jumps straight to the root. That flattening is why this is amortised α(n).

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
25 / CONCEPT #03 · DSU · HARD

Disjoint Set — Union by Rank and Size

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

The statement merges things and then asks whether two of them ended up together — accounts, islands, network cables, stones. Anywhere connectivity changes over time and you only ever query membership, never the path.

INTUITION

Represent each group as a tree and identify it by its root. find walks to the root and flattens the path behind it; unite resolves both nodes to roots and hangs the smaller tree under the larger. Those two optimisations together make the amortised cost α(n), which is under 5 for any input that exists.

STEPS
  1. par[i] = i and sz[i] = 1 — every node starts as its own singleton group
  2. find(x): walk to the root, re-pointing every node on the path at it
  3. unite(a, b): resolve BOTH to roots first, and return early if they match
  4. Hang the smaller root under the larger and add the sizes
  5. Return a bool from unite — 'did this actually merge' is the useful signal
↕ SCROLL
struct DSU {
    vector<int> par, sz;
    DSU(int n) : par(n), sz(n, 1) { iota(par.begin(), par.end(), 0); }

    int find(int x) {
        if (par[x] == x) return x;
        return par[x] = find(par[x]);   // PATH COMPRESSION: the assignment
    }                                   // is the optimisation, not the return

    bool unite(int a, int b) {
        a = find(a);                    // ALWAYS resolve to roots first —
        b = find(b);                    // linking raw nodes corrupts the forest
        if (a == b) return false;       // already together; caller counts on this

        if (sz[a] < sz[b]) swap(a, b);  // UNION BY SIZE: small hangs under large
        par[b] = a;
        sz[a] += sz[b];
        return true;                    // a real merge happened
    }
};
TIMEO(α(n))amortised per operation, with both optimisations present
SPACEO(n)the parent and size arrays
TRAP

Using union by size without path compression, or the reverse, is the quiet one. Both are individually correct and both leave you at O(log n) per operation. Code that looks fully optimised then runs an order of magnitude slower than expected under 10⁵ queries — and nothing about the output says why.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
26 / INTRO UNIT 04 · Kruskal's Algorithm

UNIT 04 — Kruskal's Algorithm

Kruskal ignores the graph's shape entirely. Sort every edge by weight, walk the list once, and take each edge unless its two endpoints are already connected. Disjoint Set is the only reason that check is cheap — which is why it had to come first.

THE QUESTION THIS LECTURE ANSWERS

IF YOU ALWAYS TAKE THE CHEAPEST EDGE THAT DOES NOT CLOSE A CYCLE, IS THE RESULT MINIMAL?

KRUSKALEDGE LISTCYCLE CHECKCUT PROPERTYGREEDY
WHAT TO WATCH FOR
  • 01THE SORT DOMINATES THE COST — O(E log E), EVERYTHING ELSE IS α(n)
  • 02'WOULD THIS CLOSE A CYCLE' IS EXACTLY find(u) == find(v)
  • 03STOP AT n−1 TAKEN EDGES — THE TAIL OF THE SORTED LIST IS WASTED WORK
  • 04KRUSKAL AND PRIM ALWAYS AGREE ON THE TOTAL, WHATEVER ORDER THEY WORK IN
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
27 / VIDEO UNIT 04 · Kruskal's Algorithm

G-47. Kruskal's Algorithm - Minimum Spanning Tree

GRAPH SERIES
Kruskal's Algorithm
RUNTIME 13:11
AFTER THIS → 3 DRILLS · PROBLEM #04, #10
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
28 / DRILL UNIT 04 · Kruskal's Algorithm · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Kruskal reaches an edge (u, v) and finds find(u) == find(v). What does that tell it?

Equal roots means a path between u and v already exists using edges taken earlier — and since the list is sorted, every one of those was cheaper. Adding this edge would create a cycle and buy nothing. Skip it. That one comparison is the whole cycle test, and without DSU it would be a graph traversal per edge.

DRILL 02 · BUG

Someone builds the edge list from an adjacency list with for u: for (v,w) in adj[u]: edges.push({w,u,v}) on an undirected graph. What goes wrong?

Each undirected edge appears in both endpoints' lists, so the list is 2E long. The total stays correct — the duplicate is rejected by the cycle check the second time — which is exactly why this survives testing and then costs you on a dense graph. Guard with if (u < v) and add each edge once.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
29 / DRILL UNIT 04 · Kruskal's Algorithm · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRACE

Run the visualiser on the previous slide to completion. How many of this graph's ten edges get rejected, and what is the final total?

Exactly one: 2–3 flashes red, because by the time the sorted list reaches it, 2 and 3 are already joined through 1–4–5–6. Ten edges, seven taken, one rejected, and the walk stops early once the seventh is in — the last two are never examined. The total lands on 24, identical to Prim's, which is the cut property doing the work.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
30 / MECHANISM UNIT 04 · Kruskal's Algorithm · CODE MIRRORED

KRUSKAL — CHEAPEST EDGE THAT DOES NOT CYCLE

Sort every edge, then take it unless its endpoints are already connected. DSU is the only thing making that check fast. Rejected edges flash red; taken edges turn green and their weights fill in.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
31 / CONCEPT #04 · MST · HARD

Find the MST Weight (Kruskal's Algorithm)

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

An MST weight is wanted and the graph is already an edge list, or converts to one cheaply. Kruskal never asks which nodes are adjacent — it only ever asks whether two nodes are already connected.

INTUITION

Sort every edge by weight and walk the list once. Take an edge only when its endpoints sit in different DSU groups; that is precisely the test for 'would this close a cycle'. After n − 1 successful takes the tree spans the graph and the rest of the list is irrelevant.

STEPS
  1. Flatten the graph to {weight, u, v}, guarding u < v so each edge appears once
  2. Sort ascending by weight — this is where the time goes
  3. For each edge, call unite(u, v); if it returns false the edge would cycle, so skip
  4. Otherwise add its weight and increment the taken count
  5. Break as soon as taken reaches n − 1
BRUTEO(2^E)
OPTIMALO(E log E)
↕ SCROLL
int spanningTree(int V, vector<vector<int>> adj[]) {
    vector<array<int,3>> edges;              // {w, u, v}
    for (int u = 0; u < V; u++)
        for (auto &e : adj[u]) {
            int v = e[0], w = e[1];
            if (u < v) edges.push_back({w, u, v});   // each edge ONCE
        }

    sort(edges.begin(), edges.end());         // by weight — the dominant cost
    DSU dsu(V);
    int total = 0, taken = 0;

    for (auto &[w, u, v] : edges) {
        // unite IS the cycle test: false means same root already
        if (!dsu.unite(u, v)) continue;
        total += w;
        if (++taken == V - 1) break;          // a spanning tree is n-1 edges
    }
    return total;
}
TIMEO(E log E)the sort dominates; the E union-find calls are α(n) each
SPACEO(V + E)the edge list plus the DSU arrays
TRAP

Calling unite before testing, then trying to undo it, is the trap — DSU has no undo. Once two trees are linked there is no cheap way to separate them. Let unite itself be the test: it returns false and changes nothing when the roots already match.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
32 / PROBLEM #10 · MST · MED

Swim in Rising Water

MED mst ▶ SOLVE ON LEETCODENot a Striver lecture — user-supplied walkthrough, since no G-44..G-56 lecture covers this problem. It solves LC 778 with Dijkstra, which is why the problem sits oddly in an MST unit: the minimax relaxation from deck 2 unit 08 also solves it.
SIGNAL — WHAT GIVES IT AWAY

You need the minimum over paths of the maximum cell on the path — a minimax question, not a sum. Phrases like 'the water level at which you can swim across' or 'the smallest ceiling that still lets you through' all point here.

INTUITION

Turn time into the loop variable. Elevations on LC 778 are a permutation of 0 … n²−1, so at time t exactly the cells with elevation ≤ t are under water. Activate cells in elevation order, union each with its already-active neighbours, and the answer is the first t at which the two corners share a root.

STEPS
  1. Bucket every cell by elevation — a permutation, so a flat array indexes directly
  2. Walk t from 0 upward, activating the single cell whose elevation is t
  3. Union it with each of its four neighbours that is already active
  4. After each step, test whether corner 0 and corner n²−1 share a root
  5. The first t at which they do is the answer
BRUTEO(n⁴ log n)
OPTIMALO(n² α)
↕ SCROLL
int swimInWater(vector<vector<int>>& grid) {
    int n = grid.size();

    // elevations are a permutation of 0..n*n-1, so this is a direct index:
    // cells[t] is the single cell that goes under water at time t
    vector<pair<int,int>> cells(n * n);
    for (int r = 0; r < n; r++)
        for (int c = 0; c < n; c++)
            cells[grid[r][c]] = {r, c};

    DSU dsu(n * n);
    vector<bool> on(n * n, false);
    int dr[] = {-1, 1, 0, 0}, dc[] = {0, 0, -1, 1};

    for (int t = 0; t < n * n; t++) {
        auto [r, c] = cells[t];
        on[r * n + c] = true;                 // this cell is now swimmable
        for (int k = 0; k < 4; k++) {
            int nr = r + dr[k], nc = c + dc[k];
            if (nr < 0 || nr >= n || nc < 0 || nc >= n) continue;
            if (on[nr * n + nc])              // ONLY already-active neighbours
                dsu.unite(r * n + c, nr * n + nc);
        }
        if (dsu.find(0) == dsu.find(n * n - 1)) return t;   // corners joined
    }
    return -1;                                // unreachable on a valid grid
}
TIMEO(n² α)each cell activates once and does at most four unions
SPACEO(n²)the DSU arrays plus the active flags
TRAP

Unioning with every neighbour rather than only the active ones silently connects the corners early, and you return a t that is too small. A neighbour that exists is not a neighbour you may swim to — check on[nr][nc] before every union.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #10 · SWIM IN RISING WATER

Swim in Rising Water - Dijkstra's Algorithm - Leetcode 778

The walkthrough for #10 Swim in Rising Water. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Swim in Rising Water - Dijkstra's Algorithm - Leetcode 778
RUNTIME 16:22
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
33 / INTRO UNIT 05 · Number of Provinces — with Disjoint Set

UNIT 05 — Number of Provinces — with Disjoint Set

Deck 1 counted provinces with DFS. Nothing was wrong with that — but watch what happens when you count them with DSU instead: you never traverse anything. Start the count at n and drop it by one every time a union actually merges two different roots. The count is a side effect of building the structure.

THE QUESTION THIS LECTURE ANSWERS

HOW DOES A CONNECTED-COMPONENT COUNT FALL OUT OF DISJOINT SET FOR FREE?

PROVINCECOMPONENTCOMPONENT COUNTADJACENCY MATRIX
WHAT TO WATCH FOR
  • 01START AT n COMPONENTS AND SUBTRACT ONE PER SUCCESSFUL UNION
  • 02A UNION WHOSE ROOTS ALREADY MATCH MUST NOT DECREMENT ANYTHING
  • 03AN ADJACENCY MATRIX ONLY NEEDS ITS UPPER TRIANGLE — j STARTS AT i+1
  • 04DFS AND DSU BOTH ANSWER THIS; DSU WINS THE MOMENT EDGES ARRIVE OVER TIME
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
34 / VIDEO UNIT 05 · Number of Provinces — with Disjoint Set

G-48. Number of Provinces - Disjoint Set

GRAPH SERIES
Number of Provinces — with Disjoint Set
RUNTIME 8:03
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
35 / DRILL UNIT 05 · Number of Provinces — with Disjoint Set · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Using DSU, where does the province count actually come from?

Both listed methods work, but the counter is the one the lecture builds because it is free — no extra pass at all. Every merge reduces the number of groups by exactly one, so maintaining the count during construction costs one subtraction. Counting self-parents afterwards is a correct fallback and an extra O(n) scan.

DRILL 02 · BUG

Someone writes for each edge: dsu.unite(i, j); count--;. What happens?

This is exactly why unite returns a bool. In a triangle 1–2, 2–3, 1–3 the third edge merges nothing, but this code still decrements — three nodes report 0 components. The decrement belongs inside the success branch: if (dsu.unite(i, j)) count--;

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
36 / DRILL UNIT 05 · Number of Provinces — with Disjoint Set · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You already solve this fine with DFS. When does DSU genuinely become the better tool?

This is the reason the sheet re-solves a problem you have already done. On a static graph DFS is O(V+E) and perfectly good. But re-running DFS after each of q edge insertions is O(q · (V+E)); DSU absorbs each insertion in α(n) and always knows the answer. Every remaining problem in this deck is that shape — connectivity that changes.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
37 / MECHANISM UNIT 05 · Number of Provinces — with Disjoint Set · CODE MIRRORED

THE SAME PROBLEM, TOLD INSTEAD OF WALKED

Traversal unit 06 answers this by walking the graph; the disjoint set is told instead, one edge at a time, and traverses nothing. Watch the road 2–5: both ends already agree, so it does nothing at all. That is why the answer comes from counting the roots at the end rather than decrementing as you go.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
38 / INTRO UNIT 06 · Number of Operations to Make Network Connected

UNIT 06 — Number of Operations to Make Network Connected

You are given cables that already exist and asked to rewire. A cable whose two ends are already connected is redundant — it can be pulled out and used anywhere. So the question is only ever: how many spare cables do I have, and how many gaps do I need to close?

THE QUESTION THIS LECTURE ANSWERS

WHEN CAN A SET OF EXISTING CABLES BE REARRANGED TO CONNECT EVERYTHING?

REDUNDANT EDGECOMPONENTSPAREn−1
WHAT TO WATCH FOR
  • 01WITH FEWER THAN n−1 CABLES IT IS IMPOSSIBLE — CHECK THAT FIRST AND RETURN −1
  • 02A CABLE WHOSE ENDPOINTS SHARE A ROOT IS A SPARE
  • 03THE ANSWER IS COMPONENTS − 1, NOT THE NUMBER OF SPARES
  • 04IF n−1 CABLES EXIST, THE SPARES ARE ALWAYS ENOUGH — THEY NEVER BIND
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
39 / VIDEO UNIT 06 · Number of Operations to Make Network Connected

G-49. Number of Operations to Make Network Connected - DSU

GRAPH SERIES
Number of Operations to Make Network Connected
RUNTIME 14:48
AFTER THIS → 3 DRILLS · PROBLEM #05
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
40 / DRILL UNIT 06 · Number of Operations to Make Network Connected · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

With c components remaining after processing every cable, how many moves are needed?

Each move joins two components into one, so going from c down to 1 takes c − 1 moves. A common wrong answer is the number of spare cables — that is the supply, not the demand. The two are only related through the feasibility check.

DRILL 02 · TRAP

Why can you check connections.size() < n - 1 and return −1 immediately, before touching the DSU?

It is not an optimisation — without it there is no −1 case at all, and you would return a positive number for an impossible input. Any connected graph on n nodes needs at least n−1 edges (§unit 01). If you hold fewer, no rearrangement can ever succeed, regardless of how they are currently placed.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
41 / DRILL UNIT 06 · Number of Operations to Make Network Connected · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

You have n−1 cables exactly, and 3 components. Are there certainly enough spares?

A forest of c components spanning n nodes uses exactly n − c edges. Holding n−1 means you have (n−1) − (n−c) = c − 1 spares — precisely the number of moves needed, every time. That is why the supply never binds and the answer is simply c−1 once feasibility passes.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
42 / MECHANISM UNIT 06 · Number of Operations to Make Network Connected · CODE MIRRORED

THE SPARE COUNT IS NEVER COMPARED AGAINST THE NEED

Unit 03's disjoint set with a different ending. Each cable is either load-bearing — it joins two separate groups — or redundant, both ends already connected, which is precisely what makes it free to move. Watch what the code never does: it counts the spares and then ignores the count. Cables used is n − c, so spares is m − (n − c), which is already at least c − 1 once you hold n−1 cables. The length check on the first line is the entire feasibility test.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
43 / PROBLEM #05 · DSU · HARD

Number of Operations to Make Network Connected

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

You are handed cables that already exist and allowed to move them. Nothing is being built from scratch, so the question is a supply-and-demand one: how many spares, and how many gaps.

INTUITION

Union every cable. One whose endpoints already share a root is redundant and can be pulled out. Finish with c components and you need c − 1 moves — and if you were handed fewer than n−1 cables to begin with, no arrangement can work.

STEPS
  1. If connections.size() < n − 1, return −1 — this case has no other detection
  2. Union both endpoints of every cable
  3. Count the remaining components (start at n, decrement on each successful union)
  4. Return components − 1
↕ SCROLL
int makeConnected(int n, vector<vector<int>>& connections) {
    // n nodes need at least n-1 edges to be connected at all. Without this
    // check there is no -1 case and impossible inputs return a number.
    if ((int)connections.size() < n - 1) return -1;

    DSU dsu(n);
    int components = n;

    for (auto &e : connections)
        if (dsu.unite(e[0], e[1]))   // only a REAL merge reduces the count
            components--;

    return components - 1;           // one move closes one gap
}
TIMEO(E · α(n))one union per cable, effectively constant each
SPACEO(n)the parent and size arrays
TRAP

Returning the number of redundant cables instead of components − 1. They are different quantities that happen to coincide on the samples: spares are the supply, gaps are the demand, and only the demand is the answer.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #05 · NUMBER OF OPERATIONS TO MAKE NETWORK CONNECTED

G-49. Number of Operations to Make Network Connected - DSU

The walkthrough for #05 Number of Operations to Make Network Connected. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-49. Number of Operations to Make Network Connected - DSU
RUNTIME 14:48
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
44 / INTRO UNIT 07 · Accounts Merge

UNIT 07 — Accounts Merge

Two accounts belong to the same person if they share any single email. That is a connectivity question wearing a data-cleaning costume. The trick is choosing what the DSU nodes are: account indices, with a map from each email to the first account that claimed it supplying the edges.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU MERGE RECORDS THAT SHARE ANY ONE FIELD?

DSU OVER INDICESEMAIL OWNER MAPBUCKET BY ROOTCANONICAL FORM
WHAT TO WATCH FOR
  • 01THE DSU IS OVER ACCOUNT INDICES — NOT OVER EMAIL STRINGS
  • 02A MAP email → FIRST OWNER IS WHAT MANUFACTURES THE EDGES
  • 03GATHER BY ROOT AT THE END, THEN SORT EACH BUCKET
  • 04THE NAME IS CARRIED ALONG, NEVER COMPARED — TWO PEOPLE CAN SHARE A NAME
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
45 / VIDEO UNIT 07 · Accounts Merge

G-50. Accounts Merge - DSU

GRAPH SERIES
Accounts Merge
RUNTIME 22:01
AFTER THIS → 3 DRILLS · PROBLEM #07
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
46 / DRILL UNIT 07 · Accounts Merge · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What are the DSU's nodes in the lecture's solution?

Accounts, not emails. Emails are the evidence that two accounts belong together, and a hash map from email to its first owner turns that evidence into edges. Making emails the nodes also works but needs string-keyed DSU and a second map back to names — more machinery for the same answer.

DRILL 02 · BUG

A solution merges any two accounts that share the same name. What breaks?

The problem states plainly that two accounts may carry the same name and still be different people — only a shared email proves identity. The name is output, never input to the decision. This produces a confidently wrong answer on the sample, which is the cheapest possible place to learn it.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
47 / DRILL UNIT 07 · Accounts Merge · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

After all unions, how do you assemble the output?

DSU tells you which group, never who is in it — there are no child pointers to walk, only parents. So you invert it: one pass over every email, appending it to a bucket keyed by find(owner). The required sort is per bucket, and the name is read from any member since they now agree.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
48 / MECHANISM UNIT 07 · Accounts Merge · CODE MIRRORED

THE NAME PROVES NOTHING. A SHARED ADDRESS PROVES EVERYTHING

The nodes are strings, and that is the whole difficulty — a disjoint set indexes integers, so every address gets an id the first time it is seen. Union each account's addresses to its own first one and two accounts sharing even a single address collapse into one component without ever being compared. Watch the third John: it merges because of an address, and Mary stays separate despite the same machinery running over her.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
49 / PROBLEM #07 · DSU · HARD

Accounts Merge

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

Records that must be merged when they share any one field. The word 'merge' plus a shared identifier is a connectivity problem in disguise — the emails are evidence, not entities.

INTUITION

Make the DSU nodes the account indices. Walk every email keeping a map from the email to the first account that claimed it; a second sighting is an edge, so union the two accounts. At the end, bucket every email under its owner's root.

STEPS
  1. DSU over account indices, 0 … accounts.size() − 1
  2. For each email: if unseen, record owner; otherwise union this account with that owner
  3. Bucket every email under find(its account) — DSU has no child pointers to walk
  4. Sort each bucket and prepend the name, read from any member of the group
BRUTEO(n² · k)
OPTIMALO(n·k·α + n·k log(n·k))
↕ SCROLL
vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
    int n = accounts.size();
    DSU dsu(n);
    unordered_map<string,int> owner;      // email -> FIRST account that claimed it

    for (int i = 0; i < n; i++)
        for (int j = 1; j < (int)accounts[i].size(); j++) {
            const string &mail = accounts[i][j];
            if (owner.count(mail)) dsu.unite(i, owner[mail]);
            else owner[mail] = i;         // the name is never consulted here
        }

    // DSU knows the group, not its members — invert it into buckets
    unordered_map<int, vector<string>> group;
    for (auto &[mail, idx] : owner) group[dsu.find(idx)].push_back(mail);

    vector<vector<string>> out;
    for (auto &[root, mails] : group) {
        sort(mails.begin(), mails.end());
        vector<string> row{accounts[root][0]};
        for (auto &m : mails) row.push_back(m);
        out.push_back(row);
    }
    return out;
}
TIMEO(n·k·α + n·k log(n·k))the final per-bucket sort dominates the unions
SPACEO(n · k)the email→owner map plus the buckets
TRAP

Merging on the name. The problem says explicitly that two different people may share a name — only a shared email proves identity. The name is output, never input to the decision, and this produces a confidently wrong answer rather than an error.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #07 · ACCOUNTS MERGE

G-50. Accounts Merge - DSU

The walkthrough for #07 Accounts Merge. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-50. Accounts Merge - DSU
RUNTIME 22:01
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
50 / INTRO UNIT 08 · Number of Islands II — Online Queries

UNIT 08 — Number of Islands II — Online Queries

Land appears one cell at a time and the island count is wanted after every addition. Re-running a traversal per query is O(q · n · m) and will not pass. DSU handles each addition in α(n): a new cell adds one island, and every successful merge with a neighbour takes one back.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU COUNT ISLANDS WHEN LAND APPEARS ONE CELL AT A TIME?

ONLINE QUERYFLATTENED INDEXINCREMENTAL COUNTDUPLICATE POSITION
WHAT TO WATCH FOR
  • 01EACH NEW CELL FIRST INCREMENTS THE COUNT, THEN MERGES PULL IT BACK DOWN
  • 02A REPEATED POSITION MUST BE IGNORED ENTIRELY, OR THE COUNT DRIFTS UPWARD
  • 03FLATTEN (r, c) TO r*m + c SO ONE FLAT DSU COVERS THE WHOLE GRID
  • 04ONLY MERGE WITH NEIGHBOURS THAT ARE ALREADY LAND
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
51 / VIDEO UNIT 08 · Number of Islands II — Online Queries

G-51. Number of Islands - II - Online Queries - DSU

GRAPH SERIES
Number of Islands II — Online Queries
RUNTIME 25:32
AFTER THIS → 3 DRILLS · PROBLEM #08
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
52 / DRILL UNIT 08 · Number of Islands II — Online Queries · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

A new cell is added and has three neighbouring cells that are all already land and all in the same island. By how much does the island count change?

The new cell adds 1. The first neighbour merges successfully (−1); the second and third already share that root, so their unions return false and change nothing. Net 0. This is exactly why the count must key off the union's return value and not the number of neighbours.

DRILL 02 · BUG

The same position appears twice in the query list and the code processes it normally both times. What goes wrong?

The +1 happens before any union is attempted, so a repeat position inflates the count and the unions then find matching roots and correct nothing. Every subsequent answer is wrong by one. Guard at the top: if the cell is already land, push the current count and continue.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
53 / DRILL UNIT 08 · Number of Islands II — Online Queries · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Why is a fresh DFS per query unacceptable here when it was fine for Number of Islands?

Nothing is wrong with DFS on a static grid — that is exactly what deck 1 used. The cost here is that the grid changes q times and DFS re-derives the whole answer from scratch each time, throwing away everything it learned. DSU keeps the answer up to date incrementally, which is the entire reason this problem sits in a DSU unit.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
54 / MECHANISM UNIT 08 · Number of Islands II — Online Queries · CODE MIRRORED

THE COUNT MOVES BOTH WAYS, ONCE PER QUERY

Every other island problem gets the grid up front and traverses it. Here land arrives one cell at a time and the answer is wanted after each arrival, which rules a traversal out. The arithmetic is the lesson: a new cell always adds one island, then each distinct neighbouring island it merges with takes one back — distinct, so two cells of the same island cost one merge, which is what find() is there to decide.

THE GRID
CALL STACK
PAINTED
RESULT
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
55 / PROBLEM #08 · DSU · HARD

Number of Islands II

HARD dsu ▶ SOLVE ON LEETCODE PREMIUMLeetCode Premium — the problem is paywalled.
SIGNAL — WHAT GIVES IT AWAY

Land is added one cell at a time and an answer is wanted after every addition. 'After each query' plus 'how many components' is the online-connectivity signature, and it rules out re-traversal.

INTUITION

Keep a flat DSU over r·m + c. Each new cell provisionally adds one island; then for every already-land neighbour, a successful union takes one back. Record the count after each query.

STEPS
  1. Flatten (r, c) to r*m + c so one DSU spans the grid
  2. If the cell is already land, push the current count and skip it entirely
  3. Mark it land and increment the count by one
  4. For each of the four neighbours that is land, union — decrement only if it merged
  5. Append the running count to the answer
BRUTEO(q · n · m)
OPTIMALO(q · α)
↕ SCROLL
vector<int> numIslands2(int n, int m, vector<vector<int>>& ops) {
    DSU dsu(n * m);
    vector<bool> land(n * m, false);
    vector<int> out;
    int count = 0;
    int dr[] = {-1, 1, 0, 0}, dc[] = {0, 0, -1, 1};

    for (auto &op : ops) {
        int r = op[0], c = op[1], id = r * m + c;
        // a repeated position must change NOTHING, or the count drifts up
        if (land[id]) { out.push_back(count); continue; }
        land[id] = true;
        count++;                          // provisionally its own island
        for (int k = 0; k < 4; k++) {
            int nr = r + dr[k], nc = c + dc[k];
            if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
            if (!land[nr * m + nc]) continue;
            if (dsu.unite(id, nr * m + nc)) count--;   // only a REAL merge
        }
        out.push_back(count);
    }
    return out;
}
TIMEO(q · α)each query does at most four unions
SPACEO(n · m)the DSU plus the land grid
TRAP

A repeated position in the query list. The +1 fires before any union is attempted, so a duplicate inflates the count permanently and every later answer is too high. Guard at the top of the loop, before incrementing.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #08 · NUMBER OF ISLANDS II

G-51. Number of Islands - II - Online Queries - DSU

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

SOLUTION WALKTHROUGH
G-51. Number of Islands - II - Online Queries - DSU
RUNTIME 25:32
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
56 / INTRO UNIT 09 · Making a Large Island

UNIT 09 — Making a Large Island

Every zero is a candidate bridge between islands. Precompute all islands and their sizes once, then test each zero by summing the sizes of its distinct neighbouring islands. Two passes, no repeated traversal — and the deduplication is where nearly everyone loses a mark.

THE QUESTION THIS LECTURE ANSWERS

WHICH SINGLE ZERO, FLIPPED TO ONE, PRODUCES THE LARGEST ISLAND?

CANDIDATE FLIPDISTINCT ROOTSCOMPONENT SIZETWO-PASS
WHAT TO WATCH FOR
  • 01TWO SEPARATE PASSES: BUILD EVERY ISLAND FIRST, THEN TEST EVERY ZERO
  • 02DEDUPLICATE NEIGHBOUR ROOTS OR ONE ISLAND IS COUNTED TWICE AND THE ANSWER INFLATES
  • 03THE ANSWER IS 1 + THE SUM OF DISTINCT NEIGHBOUR SIZES
  • 04A GRID WITH NO ZERO AT ALL HAS NO FLIP TO MAKE — ANSWER n*n
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
57 / VIDEO UNIT 09 · Making a Large Island

G-52. Making a Large Island - DSU

GRAPH SERIES
Making a Large Island
RUNTIME 26:16
AFTER THIS → 3 DRILLS · PROBLEM #09
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
58 / DRILL UNIT 09 · Making a Large Island · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

A zero has two neighbours that both belong to the same island of size 5. The code sums neighbour sizes without deduplicating. What does it report?

1 + 5 + 5 = 11, when flipping that cell really produces an island of 1 + 5 = 6. The same island reached through two different sides is still one island. Collect find(neighbour) into a set first, then sum over the set — never over the neighbour list.

DRILL 02 · RECALL

Why are the islands built in a separate pass before any zero is examined?

Testing a zero by flipping it and running a traversal would be O(n²) per candidate and O(n⁴) overall. Building the DSU once gives every island a root with a known size, so a candidate costs four find calls. The whole solution lands at O(n²).

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
59 / DRILL UNIT 09 · Making a Large Island · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRAP

The grid is entirely ones. What does a loop that only considers zeros return?

The answer loop never executes, so whatever the accumulator was initialised to is returned — typically 0. The grid is already one island of n·n and that is the answer. Seed the result with the largest existing island before testing any candidate and the case resolves itself with no special branch.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
60 / MECHANISM UNIT 09 · Making a Large Island · CODE MIRRORED

DISTINCT IS THE WHOLE WORD

Two passes. Label every island and record its size; then for each water cell sum the islands around it. The second pass hinges on one word: the neighbouring labels go into a set before they are summed, because a cell can touch the same island twice. Watch the flip at (1,1) — two land neighbours, one island, counted once. Adding per neighbouring cell instead looks right until an input has touching neighbours.

THE GRID
CALL STACK
PAINTED
RESULT
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
61 / PROBLEM #09 · DSU · HARD

Making a Large Island

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

One flip allowed, and you are asked for the best achievable size. 'Change exactly one cell to maximise a component' means precompute the components, then price each candidate — never re-explore per candidate.

INTUITION

Build every island once with DSU so each root carries a size. Then for each zero, look at its four neighbours, collect their roots into a set, and the flipped size is 1 plus the sum over that set. Deduplication is the whole problem.

STEPS
  1. Union all adjacent land cells so every island has a root with a size
  2. Seed the answer with the largest island — covers the grid with no zeros
  3. For each zero, gather find(neighbour) for the four sides into a set
  4. Candidate = 1 + the sum of sizes over the distinct roots
  5. Keep the maximum
BRUTEO(n⁴)
OPTIMALO(n²)
↕ SCROLL
int largestIsland(vector<vector<int>>& grid) {
    int n = grid.size();
    DSU dsu(n * n);
    int dr[] = {-1, 1, 0, 0}, dc[] = {0, 0, -1, 1};

    // PASS 1 — build every island so each root knows its size
    for (int r = 0; r < n; r++)
        for (int c = 0; c < n; c++) {
            if (!grid[r][c]) continue;
            for (int k = 0; k < 4; k++) {
                int nr = r + dr[k], nc = c + dc[k];
                if (nr < 0 || nr >= n || nc < 0 || nc >= n) continue;
                if (grid[nr][nc]) dsu.unite(r * n + c, nr * n + nc);
            }
        }

    // seed with the biggest existing island: an all-ones grid has no zero to flip
    int best = 0;
    for (int i = 0; i < n * n; i++)
        if (dsu.find(i) == i) best = max(best, dsu.sz[i]);

    // PASS 2 — price every candidate flip
    for (int r = 0; r < n; r++)
        for (int c = 0; c < n; c++) {
            if (grid[r][c]) continue;
            set<int> roots;               // DISTINCT — the same island twice is once
            for (int k = 0; k < 4; k++) {
                int nr = r + dr[k], nc = c + dc[k];
                if (nr < 0 || nr >= n || nc < 0 || nc >= n) continue;
                if (grid[nr][nc]) roots.insert(dsu.find(nr * n + nc));
            }
            int total = 1;
            for (int root : roots) total += dsu.sz[root];
            best = max(best, total);
        }
    return best;
}
TIMEO(n²)two passes over the grid, four finds per cell
SPACEO(n²)the DSU over all n² cells
TRAP

Summing neighbour sizes without deduplicating roots. A zero touching the same island on two sides counts it twice — 1+5+5 = 11 where the truth is 6. It inflates the answer on exactly the shapes the tests contain.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #09 · MAKING A LARGE ISLAND

G-52. Making a Large Island - DSU

The walkthrough for #09 Making a Large Island. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-52. Making a Large Island - DSU
RUNTIME 26:16
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
62 / INTRO UNIT 10 · Most Stones Removed with Same Row or Column

UNIT 10 — Most Stones Removed with Same Row or Column

Stones in the same row or column can be removed down to one survivor per group. So the answer is n − (number of components). The elegant part is what you union: not stones with stones, but each stone's row with its column.

THE QUESTION THIS LECTURE ANSWERS

HOW MANY STONES CAN BE REMOVED IF EACH REMOVAL NEEDS A ROW OR COLUMN PARTNER?

ROW/COL NODESOFFSETCOMPONENTSURVIVOR
WHAT TO WATCH FOR
  • 01EVERY COMPONENT MUST LEAVE EXACTLY ONE STONE STANDING — HENCE n − COMPONENTS
  • 02UNION THE ROW INDEX WITH THE COLUMN INDEX, NOT STONE WITH STONE
  • 03OFFSET COLUMNS (col + 10001) SO ROW 3 AND COLUMN 3 ARE DIFFERENT NODES
  • 04COUNT COMPONENTS OVER ROWS AND COLUMNS THAT ACTUALLY HOLD A STONE
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
63 / VIDEO UNIT 10 · Most Stones Removed with Same Row or Column

G-53. Most Stones Removed with Same Row or Column - DSU

GRAPH SERIES
Most Stones Removed with Same Row or Column
RUNTIME 23:51
AFTER THIS → 3 DRILLS · PROBLEM #06
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
64 / DRILL UNIT 10 · Most Stones Removed with Same Row or Column · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why is the answer n − components rather than n − 1?

Removal requires a partner sharing a row or column, so you can strip a component down until one stone remains — but never past it, and no stone can help across components. k components therefore leave k survivors, and n − k come off.

DRILL 02 · BUG

Someone unions row with col directly, with no offset. What breaks?

They share an index space. A stone at (3, 7) and a stone at (7, 3) would be joined through the shared node 3 and 7 despite having no row or column in common. Offset one space past the other's maximum — col + 10001 for this problem's bounds — so the two families of nodes can never collide.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
65 / DRILL UNIT 10 · Most Stones Removed with Same Row or Column · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Why union a row with a column at all, rather than comparing every pair of stones?

Pairwise is correct but quadratic. Every stone at (r, c) is evidence that row r and column c are in the same group, so a single union per stone captures all of it — every stone in row r is then transitively connected without ever being compared. n unions replace n²/2 comparisons.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
66 / MECHANISM UNIT 10 · Most Stones Removed with Same Row or Column · CODE MIRRORED

THE EDGES ARE NEVER BUILT

Two stones are connected when they share a row or a column, and comparing every pair to discover that would be O(n²). So it is not done. Each stone is unioned with its row key and its column key instead — never with another stone — and anything sharing either key lands in one component transitively. The answer is n minus the component count, because exactly one stone per component has to stay behind.

THE GRID
CALL STACK
PAINTED
RESULT
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
67 / PROBLEM #06 · DSU · MED

Most Stones Removed with Same Row or Column

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

Removal needs a partner sharing a row or a column. Whenever the rule is 'you may act while a neighbour remains', the answer is almost always total minus the number of independent groups.

INTUITION

Each group can be stripped to a single survivor and no further, so the answer is n − components. Rather than compare stones pairwise, note that a stone at (r, c) is itself the evidence that row r and column c belong together — so union those two, once per stone.

STEPS
  1. Treat every row index and every column index as a DSU node
  2. Offset the columns past the rows (col + 10001) so the two spaces cannot collide
  3. For each stone, union its row node with its column node
  4. Count distinct roots among the rows and columns that actually hold a stone
  5. Return n − that count
BRUTEO(n²)
OPTIMALO(n · α)
↕ SCROLL
int removeStones(vector<vector<int>>& stones) {
    DSU dsu(20002);                  // rows 0..10000, cols offset above them
    unordered_set<int> used;

    for (auto &s : stones) {
        int r = s[0], c = s[1] + 10001;   // OFFSET: row 3 and col 3 must differ
        dsu.unite(r, c);
        used.insert(r);
        used.insert(c);
    }

    unordered_set<int> roots;
    for (int node : used) roots.insert(dsu.find(node));

    // every component leaves exactly one stone standing
    return stones.size() - roots.size();
}
TIMEO(n · α)one union per stone rather than a comparison per pair
SPACEO(n)DSU over the row and column index space
TRAP

Forgetting the column offset. Row 3 and column 3 then become the same DSU node, so a stone at (3, 7) and one at (7, 3) get joined despite sharing nothing. The count comes out too low and the answer too high — with no crash to warn you.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #06 · MOST STONES REMOVED WITH SAME ROW OR COLUMN

G-53. Most Stones Removed with Same Row or Column - DSU

The walkthrough for #06 Most Stones Removed with Same Row or Column. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-53. Most Stones Removed with Same Row or Column - DSU
RUNTIME 23:51
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
68 / INTRO UNIT 11 · Strongly Connected Components — Kosaraju

UNIT 11 — Strongly Connected Components — Kosaraju

A strongly connected component is a group where every node can reach every other — which only means anything on a directed graph. Kosaraju finds them in two passes and one reversal: order the nodes by finish time, flip every edge, then DFS in that order.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND GROUPS WHERE EVERY NODE CAN REACH EVERY OTHER, BOTH WAYS?

SCCFINISH TIMETRANSPOSECONDENSATION
WHAT TO WATCH FOR
  • 01THREE STEPS: ORDER BY FINISH TIME · REVERSE THE EDGES · DFS IN THAT ORDER
  • 02THE STACK HOLDS FINISH ORDER, NOT VISIT ORDER — PUSH AFTER THE RECURSION
  • 03REVERSING LEAVES EACH SCC INTACT BUT CUTS THE ONE-WAY PATHS BETWEEN THEM
  • 04EACH DFS ON THE REVERSED GRAPH EXACTLY EXHAUSTS ONE COMPONENT
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
69 / VIDEO UNIT 11 · Strongly Connected Components — Kosaraju

G-54. Strongly Connected Components - Kosaraju's Algorithm

GRAPH SERIES
Strongly Connected Components — Kosaraju
RUNTIME 22:44
AFTER THIS → 3 DRILLS · PROBLEM #13
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
70 / DRILL UNIT 11 · Strongly Connected Components — Kosaraju · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

When is a node pushed onto the stack in Kosaraju's first pass?

After the recursive calls, not before. The stack must hold nodes in decreasing finish time, so that popping it gives a node from a source component of the condensation first. Push on entry instead and you get visit order, the guarantee collapses, and components silently merge.

DRILL 02 · RECALL

Why does reversing every edge not change which nodes are strongly connected?

If u reaches v and v reaches u, both paths simply run backwards after reversal — so the SCCs are identical. What changes is the traffic between components: a one-way link A → B becomes B → A, so starting from a former source component the DFS can no longer leak into its neighbours, and it collects exactly one SCC.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
71 / DRILL UNIT 11 · Strongly Connected Components — Kosaraju · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRAP

Would running the second DFS on the original graph, in the same stack order, also work?

The order and the reversal are both load-bearing, and each is useless alone. The stack hands you a node in a component that can reach the others; on the original graph, DFS from there walks straight into all of them. Reversal is what turns those exits into entrances so the traversal is trapped in one component — which is precisely what you want to collect.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
72 / MECHANISM UNIT 11 · Strongly Connected Components — Kosaraju · CODE MIRRORED

KOSARAJU — TWO PASSES AND A REVERSAL

Watch the arrowheads. Pass 1 stacks nodes by finish time; then every edge flips, and each DFS from the stack top is trapped inside exactly one component. The reversal is the moment it clicks — the exits become entrances.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
73 / PROBLEM #13 · SCC · HARD

Kosaraju's Algorithm — Strongly Connected Components

HARD scc ▶ SHEET'S PROBLEM · LC 1520▶ DRILL KOSARAJU ON GFGThe sheet pairs this row with LC 1520, which is a greedy interval problem rather than an SCC exercise. Both links ship: LC 1520 so the row reconciles with takeuforward, and the GfG problem so the algorithm the unit teaches actually gets practised.
SIGNAL — WHAT GIVES IT AWAY

A directed graph and a question about mutual reachability — 'every node can reach every other within the group'. On an undirected graph this would just be connected components; direction is what makes it hard.

INTUITION

Two passes and a reversal. The first DFS records finish times on a stack, so popping it yields a node from a component nothing else points into. Reversing every edge leaves the components untouched but turns their exits into entrances, so a DFS from that node is trapped inside exactly one SCC.

STEPS
  1. DFS the original graph, pushing each node AFTER its recursion finishes
  2. Build the transpose by reversing every edge
  3. Clear the visited marks
  4. Pop the stack; each unvisited node starts a DFS on the transpose
  5. Every such DFS collects exactly one strongly connected component
BRUTEO(V · (V + E))
OPTIMALO(V + E)
↕ SCROLL
int kosaraju(int V, vector<vector<int>>& adj) {
    vector<bool> vis(V, false);
    stack<int> order;

    // PASS 1 — push AFTER the recursion: this is finish order, not visit order
    function<void(int)> dfs1 = [&](int v) {
        vis[v] = true;
        for (int to : adj[v])
            if (!vis[to]) dfs1(to);
        order.push(v);
    };
    for (int i = 0; i < V; i++)
        if (!vis[i]) dfs1(i);

    // reverse every edge: the SCCs survive, the links between them flip
    vector<vector<int>> rev(V);
    for (int v = 0; v < V; v++)
        for (int to : adj[v])
            rev[to].push_back(v);

    // PASS 2 — each DFS on the transpose is trapped in exactly one SCC
    vis.assign(V, false);
    int scc = 0;
    function<void(int)> dfs2 = [&](int v) {
        vis[v] = true;
        for (int to : rev[v])
            if (!vis[to]) dfs2(to);
    };

    while (!order.empty()) {
        int v = order.top();
        order.pop();
        if (vis[v]) continue;
        dfs2(v);
        scc++;
    }
    return scc;
}
TIMEO(V + E)two full traversals plus building the transpose
SPACEO(V + E)the transpose, the stack and the visited array
TRAP

Pushing onto the stack on entry instead of after the recursion. That records visit order rather than finish order, the source-component guarantee is lost, and separate components silently merge into one — a plausible, smaller answer with nothing to signal the error. Two links on this slide: LC 1520 is the problem the sheet pairs with this row and is a greedy interval question, not an SCC one — the GfG link is where you actually practise the algorithm above.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #13 · KOSARAJU'S ALGORITHM — STRONGLY CONNECTED COMPONENTS

G-54. Strongly Connected Components - Kosaraju's Algorithm

The walkthrough for #13 Kosaraju's Algorithm — Strongly Connected Components. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-54. Strongly Connected Components - Kosaraju's Algorithm
RUNTIME 22:44
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
74 / INTRO UNIT 12 · Bridges in a Graph — Tarjan

UNIT 12 — Bridges in a Graph — Tarjan

A bridge is an edge whose removal disconnects the graph. Tarjan finds every one of them in a single DFS using two numbers per node: tin, when you arrived, and low, the earliest arrival time reachable from this subtree without using the edge you came in on.

THE QUESTION THIS LECTURE ANSWERS

WHICH EDGES, IF CUT, WOULD SPLIT THE GRAPH INTO PIECES?

BRIDGEtinlowBACK EDGETARJAN
WHAT TO WATCH FOR
  • 01tin IS WHEN YOU ARRIVED; low IS THE EARLIEST YOU CAN GET BACK TO
  • 02low[child] > tin[node] MEANS NO BACK EDGE BYPASSES THIS EDGE — IT IS A BRIDGE
  • 03STRICTLY GREATER FOR BRIDGES; GREATER-OR-EQUAL IS THE ARTICULATION RULE
  • 04SKIP THE EDGE YOU CAME IN ON, BUT ONLY THAT ONE INSTANCE
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
75 / VIDEO UNIT 12 · Bridges in a Graph — Tarjan

G-55. Bridges in Graph - Using Tarjan's Algorithm of time in and low time

GRAPH SERIES
Bridges in a Graph — Tarjan
RUNTIME 23:25
AFTER THIS → 3 DRILLS · PROBLEM #11
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
76 / DRILL UNIT 12 · Bridges in a Graph — Tarjan · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What exactly does low[v] hold?

It is a time, not a node id, and the exclusion is the whole point. If some node beneath v can hop back to an ancestor via a back edge, that ancestor's early tin propagates up into low[v], proving an alternative route exists and the tree edge above v is therefore not a bridge.

DRILL 02 · BUG

A student writes if (low[child] >= tin[node]) when collecting bridges. What is reported?

low[child] == tin[node] means the child can climb back to this very node by another route — so the edge is redundant and removing it disconnects nothing. Strict > is required. The confusing part is that >= is exactly right for articulation points, one lecture later; the two rules differ by a single character and answer different questions.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
77 / DRILL UNIT 12 · Bridges in a Graph — Tarjan · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRAP

Why must the parent edge be skipped by identity rather than by simply comparing node numbers?

A duplicate edge between u and v means the connection survives cutting either copy, so neither is a bridge. Skipping every neighbour equal to the parent hides the second copy, low never gets updated through it, and the edge is wrongly reported. Track the edge index you entered by and skip only that.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
78 / MECHANISM UNIT 12 · Bridges in a Graph — Tarjan · CODE MIRRORED

TIN AND LOW — WATCH THE NUMBERS DECIDE

One DFS over a graph with a cycle and two chains. Every node gets tin on entry; low starts equal and gets pulled down by back edges. Every chain edge gets cut; not one edge of the cycle does — read the two rows under the graph and the rule does itself.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
79 / PROBLEM #11 · SCC · HARD

Bridges in a Graph

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

'Critical connection', 'the network splits if this link fails', 'find every edge whose removal disconnects'. A question about edges whose loss matters is a bridge problem, and one DFS answers it.

INTUITION

Run a DFS recording tin[v], the time you arrived, and low[v], the earliest arrival time reachable from v's subtree without reusing the edge you came in on. If a child cannot reach any higher than the node itself, the edge between them is the only route — a bridge.

STEPS
  1. DFS with a global timer, setting tin[v] = low[v] = timer++ on entry
  2. Skip only the specific edge you entered by, tracked by index, not by node value
  3. For a tree edge, recurse then low[v] = min(low[v], low[child])
  4. For a back edge, low[v] = min(low[v], tin[neighbour]) — tin, not low
  5. After recursing, low[child] > tin[v] means (v, child) is a bridge
BRUTEO(E · (V + E))
OPTIMALO(V + E)
↕ SCROLL
class Solution {
    vector<vector<pair<int,int>>> adj;   // {neighbour, edge id}
    vector<int> tin, low;
    vector<vector<int>> out;
    int timer = 0;

    void dfs(int v, int inEdge) {
        tin[v] = low[v] = timer++;

        for (auto [to, id] : adj[v]) {
            // skip the edge we came in on BY ID — a parallel edge is a real
            // second route and must still be followed
            if (id == inEdge) continue;
            if (tin[to] != -1) {
                low[v] = min(low[v], tin[to]);      // back edge: tin, not low
            } else {
                dfs(to, id);
                low[v] = min(low[v], low[to]);
                // STRICTLY greater: reaching v itself would mean another route
                if (low[to] > tin[v]) out.push_back({v, to});
            }
        }
    }
public:
    vector<vector<int>> criticalConnections(int n, vector<vector<int>>& conns) {
        adj.assign(n, {});
        tin.assign(n, -1);
        low.assign(n, -1);

        for (int i = 0; i < (int)conns.size(); i++) {
            adj[conns[i][0]].push_back({conns[i][1], i});
            adj[conns[i][1]].push_back({conns[i][0], i});
        }

        dfs(0, -1);
        return out;
    }
};
TIMEO(V + E)a single DFS; every edge is examined twice
SPACEO(V + E)the adjacency list, tin, low and the recursion stack
TRAP

Using >= instead of >. Equality means the child can climb back to this very node another way, so the edge is redundant, not critical — and you report bridges that are not. Confusingly, >= is exactly right for articulation points in the next unit.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
SOLUTION #11 · BRIDGES IN A GRAPH

G-55. Bridges in Graph - Using Tarjan's Algorithm of time in and low time

The walkthrough for #11 Bridges in a Graph. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
G-55. Bridges in Graph - Using Tarjan's Algorithm of time in and low time
RUNTIME 23:25
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
80 / INTRO UNIT 13 · Articulation Points

UNIT 13 — Articulation Points

An articulation point is a node whose removal disconnects the graph — the vertex version of the previous unit, computed by the same DFS with two small changes: the comparison loosens to >=, and the root is judged by how many DFS children it has.

THE QUESTION THIS LECTURE ANSWERS

WHICH NODES, IF REMOVED, WOULD SPLIT THE GRAPH INTO PIECES?

ARTICULATION POINTCUT VERTEXDFS CHILDROOT CASE
WHAT TO WATCH FOR
  • 01low[child] >= tin[node] HERE — BRIDGES USED STRICTLY GREATER. ONE CHARACTER APART
  • 02THE ROOT IS AN ARTICULATION POINT ONLY IF IT HAS TWO OR MORE DFS CHILDREN
  • 03MARK IN A BOOLEAN ARRAY, DO NOT COUNT — A NODE IS REACHED FROM SEVERAL CHILDREN
  • 04A LEAF IS NEVER AN ARTICULATION POINT
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
81 / VIDEO UNIT 13 · Articulation Points

G-56. Articulation Point in Graph

GRAPH SERIES
Articulation Points
RUNTIME 22:00
AFTER THIS → 3 DRILLS · PROBLEM #12
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
82 / DRILL UNIT 13 · Articulation Points · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does the articulation test use >= where the bridge test used >?

The two tests ask different questions. If the child can climb back only as far as this node, cutting the edge leaves that alternate route intact — not a bridge. But deleting the node destroys the route too, so the child is stranded — an articulation point. Equality is exactly the case that separates them.

DRILL 02 · TRAP

Why is the DFS root handled by a separate rule?

Every other node inherits the test from its parent edge; the root has none, and applying the comparison to it flags it almost always. The right question for the root is whether it is holding separate subtrees together: with two or more DFS children it is, and removing it splits them apart. With one, the rest of the graph stays connected without it.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
83 / DRILL UNIT 13 · Articulation Points · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

Someone increments a counter each time the condition fires, then returns the count. What is wrong?

A node with three children can satisfy low[child] >= tin[node] for each of them and get counted three times, for one cut vertex. Set a booleanisAP[node] = true — and count the marks afterwards. The same shape of bug as double-counting islands in unit 09.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
84 / MECHANISM UNIT 13 · Articulation Points · CODE MIRRORED

THE SAME DFS, MOVED FROM THE EDGE TO THE NODE

Deliberately the same graph unit 12 used, because the pair is the lesson. The run starts at 6 rather than 1 so the case that separates the two tests actually happens: when it returns to 2, low[1] lands exactly on tin[2] — the subtree climbs back as far as 2 and no further. Cutting that edge leaves the route intact, so it is not a bridge; deleting the node destroys the route with it, so 2 is a cut vertex. And watch the root: 6 satisfies the same comparison and is still not an articulation point, which is why it needs a rule of its own.

THE SAME GRAPH, EVERY ALGORITHM
CODE MIRROR
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
85 / CONCEPT #12 · SCC · HARD

Articulation Point in a Graph

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

'Which servers, if they fail, split the network' — the vertex form of the previous question. Same DFS, same two timestamps, two small changes to the rule.

INTUITION

Identical machinery to bridges. The comparison loosens to >=, because removing the node also destroys the route back to itself that saved the edge. The DFS root has no parent edge, so it is judged separately: it is a cut vertex only if it has two or more DFS children.

STEPS
  1. DFS recording tin and low exactly as for bridges
  2. For each tree edge, if low[child] >= tin[v] and v is not the root, mark v
  3. Count the root's DFS children; mark the root if there are two or more
  4. Mark in a boolean array — a node can satisfy the test via several children
  5. Collect the marked nodes at the end
↕ SCROLL
class Solution {
    vector<vector<int>> adj;
    vector<int> tin, low;
    vector<bool> isAP;
    int timer = 0, root = 0, rootKids = 0;

    void dfs(int v, int parent) {
        tin[v] = low[v] = timer++;

        for (int to : adj[v]) {
            if (to == parent) continue;
            if (tin[to] != -1) {
                low[v] = min(low[v], tin[to]);
                continue;
            }
            dfs(to, v);
            low[v] = min(low[v], low[to]);
            // >= not > : removing the NODE kills the route back to itself too
            if (low[to] >= tin[v] && v != root) isAP[v] = true;
            if (v == root) rootKids++;
        }
    }
public:
    vector<int> articulationPoints(int n, vector<vector<int>>& edges) {
        adj.assign(n, {});
        tin.assign(n, -1);
        low.assign(n, 0);
        isAP.assign(n, false);

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

        dfs(root, -1);
        // the root has no parent edge, so it is judged on its child count alone
        if (rootKids > 1) isAP[root] = true;

        vector<int> out;
        for (int i = 0; i < n; i++)
            if (isAP[i]) out.push_back(i);
        return out;
    }
};
TIMEO(V + E)one DFS, each edge seen twice
SPACEO(V + E)tin, low, the mark array and the recursion stack
TRAP

Counting instead of marking. A node with three qualifying children is counted three times for one cut vertex. Set isAP[v] = true and total the marks afterwards — the same double-counting shape as the distinct-roots trap in unit 09.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
87 / RECALL RETRIEVAL, NOT RECOGNITION

PICK THE TOOL FROM THE STATEMENT

DRILL 01 · TRANSFER

A statement gives you a weighted graph and asks for the cheapest set of connections that keeps every node reachable. What is it asking for, and what is it not?

An MST — and it is not a shortest-path problem. The distinction matters because the MST minimises the total weight of the chosen edges, while a shortest-path tree minimises each individual distance from a source. Those are different answers, and the path an MST gives you between two nodes is frequently not the shortest one. Unit 01's drill makes exactly this point; every wrong reach for Dijkstra here starts by missing it.

DRILL 02 · RECALL

Kosaraju and Tarjan both find structure by running a DFS and watching numbers. What single quantity does each one actually rely on?

Kosaraju uses finish times; Tarjan uses low-link values. Kosaraju stacks nodes as their recursion completes, then walks the reversed graph in that order. Tarjan instead tracks, for each node, the earliest arrival time reachable from its subtree — and compares that against the node's own arrival to spot bridges and articulation points. Same traversal, two different bookkeeping ideas, and knowing which is which is most of what units 11 to 13 ask of you.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
88 / TRAPS PLAUSIBLE, WRONG, AND SILENT

THE SIX THAT ACTUALLY BITE

Every one of these returns a plausible number rather than crashing. That is what makes them expensive — the judge says no and the code looks fine.

UNITING NODES, NOT ROOTS

par[b] = a without resolving both through find first re-hangs a node's whole subtree somewhere else. Groups that were connected quietly stop being connected, and the sizes drift. It never crashes.

DECREMENTING ON EVERY UNION

The component count must only fall when a union actually merges. Edges inside a component still call unite, so a counter that ignores the return value walks below the true answer.

ONE OPTIMISATION, NOT BOTH

Union by size without path compression — or the reverse — is correct and leaves you at O(log n) per operation. The code looks fully optimised and runs an order of magnitude slow under 10⁵ queries.

COUNTING AN ISLAND TWICE

A zero touching the same island on two sides must count it once. Summing the neighbour list instead of the set of distinct roots gives 1+5+5 = 11 where the answer is 6.

>= WHERE > WAS MEANT

Bridges need low[child] > tin[v]; articulation points need >=. One character apart, both compile, both return a plausible list, and only one of them answers the question you were asked.

PUSHING ON ENTRY IN KOSARAJU

The stack must hold finish order, so the push belongs after the recursion. Push on entry and you get visit order, the source-component guarantee dies, and separate SCCs merge into one.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
89 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

The slide to reopen the night before: every tool in this deck, its cost, and the sentence in the statement that selects it.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
DSU · find + unite
O(α(n))
O(n)
Anything asking whether two things are already connected, especially over time.
Kruskal
O(E log E)
O(V + E)
MST weight and the graph is an edge list. The sort dominates; DSU does the cycle test.
Prim
O(E log V)
O(V + E)
MST weight and the graph is an adjacency list. No sorting, no DSU, one growing tree.
DSU component count
O(E · α)
O(n)
Provinces, network cables, accounts merge — start at n, subtract one per real merge.
DSU over a grid
O(n·m·α)
O(n · m)
Islands appearing one at a time, or one flip to maximise. Flatten (r,c) to r*m+c.
Activate in weight order + DSU
O(n² α)
O(n²)
Minimax on a path — the smallest ceiling that still lets you through.
Kosaraju
O(V + E)
O(V + E)
Strongly connected components on a DIRECTED graph. Finish order, reverse, DFS again.
Tarjan · tin and low
O(V + E)
O(V + E)
Bridges and articulation points. One DFS; > for edges, >= for vertices.
INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
90 / CLOSE STEP 15 · DECK 3 OF 3

SPANNING SETS — DONE

Thirteen units, 13 sheet problems, and underneath almost all of them one question asked in near-constant time — are these two already connected? If you remember one thing: reach for DSU the moment connectivity starts changing over time.

00%
OF THIS DECK SOLVED
← DECK 1 · TRAVERSAL← DECK 2 · SHORTEST PATHSALL TOPICS

That completes Step 15. Graphs across three decks: traversal, shortest paths, and spanning trees with disjoint set.

INVARIANT · GRAPHS · MST · DSU · SCC · DECK 3 OF 3
■ TOO SMALL TO READ
1280×720
INVARIANT · STEP 15 · DECK 3 OF 3

This one needs a laptop

Not a preference — an arithmetic one. Every slide is a fixed 1280 × 720 stage: a graph animating beside the code that drives it, with the problems laid out two columns wide. It scales as a single piece, so on a screen this size the body text comes out around 4px tall.

Shrinking it further would not help, and rebuilding it to reflow would mean losing the thing that makes it worth reading.

YOUR SCREEN0 × 0
NEEDED1060 × 610
WHAT IS WAITING ON THE LAPTOP
WATCH IT RUN · THEN RUN IT FROM MEMORY
Your progress is saved per device, so anything you tick on the laptop will be waiting there.