Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Group cities into connected components from the road list, then decide each component independently. Inside a component of size s you must place at least one library; the cheapest way to reach all s cities is either s libraries (s × c_lib) or one library plus s − 1 roads (c_lib + (s − 1) × c_road). Take the per-component minimum and sum. Union-Find finds the components in O(E · α(N)).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you see that the graph decomposes into independent connected components and that each component reduces to a tiny greedy choice, rather than reaching for an expensive shortest-path or MST routine.
Where candidates slip
Forgetting that every component still needs one library even when roads are cheaper, or using a 32-bit int for the total and overflowing on large inputs.
How to stand out
Call out the `c_lib ≤ c_road` shortcut immediately, derive 'span s cities with s − 1 roads' as a spanning tree, and explain why components are independent so the costs simply add.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewComponent of size sc_lib ≤ c_road?YesNoExplore the full treeCollapse the tree
How to read it:Component {1,2,3,4} and the lone city 5. Each is solved on its own and the costs are added.
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
DFS / BFS Component Sizing (Baseline)
1Hint 1Explore hintHide hint
Strip away the cost question for a moment. What you really need from the graph is: how many cities are in each connected group?
2Hint 2Explore hintHide hint
Build an adjacency list, then walk every unvisited city, flood-filling its whole component and counting how many cities you touch.
3Hint 3Explore hintHide hint
For each component size s you collect, add min(s × c_lib, c_lib + (s − 1) × c_road) to the answer.
A · Core idea
The graph problem hidden inside this question is simply component sizing. Once you know how many cities sit in each connected group, the money question is pure arithmetic.
Why a flood fill gives the sizes. Start at any unvisited city and walk every city reachable from it through roads, marking each visited and counting as you go. When the walk stalls, you have one full component and its size. Pick the next unvisited city and repeat. Every city is reached exactly once, so the sizes you collect partition all n cities.
Why the traversal must be iterative here. With up to 10⁵ cities a recursive DFS can nest 10⁵ frames deep on a long chain of roads and overflow the call stack. Use an explicit stack (or a queue for BFS) so the depth lives in the heap, not the call stack. The order of visiting does not matter — DFS and BFS both produce the same component sizes.
Why the cost is per-component and additive. A road can only ever join two cities that are already in the same component (that is what a road does). So no spending in one component can help another, and the global minimum is just the sum of each component's own minimum. Inside a component of size s you compare two complete plans: a library in all s cities, or a single library plus the s − 1 roads that connect the rest to it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 5, c_lib = 6, c_road = 1, roads = [[1,2],[1,3],[1,4]]Interactive diagram
Flood fill discovering one component
start city 1Starting at city 1, the iterative DFS reaches 2, 3, 4 — size 4.
15C · Code with teaching comments
Reference implementation previewpublic long roadsAndLibraries(int n, int cLib, int cRoad, int[][] roads) { List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i <= n; i++) adj.add(new ArrayList<>()); for (int[] r : roads) { adj.get(r[0]).add(r[1]);Explore the full codeHide the full code
D · Why it works and complexityO(N + E) timeO(N + E) spaceOpen the invariant and proofExplore the proofHide the proof
The whole problem is 'find component sizes, then apply a fixed two-line formula'. Recognizing that the cost decomposes per component is the real insight — the traversal is just bookkeeping.
ComplexityO(N + E) time, O(N + E) space — Building the adjacency list is O(N + E). Each vertex and edge is visited once during the traversal, so the flood fill is O(N + E). The cost arithmetic per component is O(1).
E · Edge cases and pitfalls3Recursive DFS on a 10⁵-city chain — stack overflow. Use an explicit stack or BFS.Explore edge casesHide edge cases
Pitfalls
- Recursive DFS on a 10⁵-city chain — stack overflow. Use an explicit stack or BFS.
- Adding only one direction of each road — splits a component in two and overprices it.
- Computing size × c_lib in 32-bit int — overflows around 2×10⁹.
Approach 2 of 2 · Optimal
Union-Find Component Sizing (Optimal)
1Hint 1Explore hintHide hint
You never need the actual roads after grouping — only which cities ended up together. What structure answers 'are these two in the same group?' in near-constant time?
2Hint 2Explore hintHide hint
Union-Find: union the two endpoints of every road, with path compression and union by rank. After processing all roads, the size of each root's set is its component size.
3Hint 3Explore hintHide hint
Walk every city, find its root, and tally how many cities map to each root. Then apply min(s × c_lib, c_lib + (s − 1) × c_road) per root.
A · Core idea
Union-Find (a disjoint-set forest) is the tool when the only question about the graph is 'which things are connected'. It never materializes paths, so it sidesteps all traversal and the stack-depth worry entirely.
How the grouping works. Each city starts as its own set. For every road [u, v] you union the two endpoints — merging their sets if they differ. After all roads are processed, two cities share a root exactly when a chain of roads connects them. The size[] array, carried on each root, then tells you how big each component is.
Why union by rank plus path compression matters. A naïve find that walks parents to the root can degrade to O(N) on a skewed tree, making the whole run O(N·E). Path compression flattens the tree during find (pointing nodes at their grandparent), and union by rank attaches the shorter tree under the taller one. Together they pin each operation at O(α(N)), which is at most 4 for any input you will ever see — effectively constant.
Why the find loop is iterative. find climbs parent pointers in a while loop, never recursing. On 10⁵ cities that avoids any chance of a deep recursive call blowing the stack — the same defensive choice as the iterative DFS, applied to the set-find.
Reading the answer. Sweep all n cities, find each city's root, and accumulate the count per root. Each distinct root is one component of some size s, and you add min(s × c_lib, c_lib + (s − 1) × c_road) for it. Sum across roots for the total. The c_lib ≤ c_road shortcut still applies — when libraries are cheaper, every component picks the s × c_lib branch and the total collapses to n × c_lib.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 6, c_lib = 2, c_road = 5, roads = [[1,3],[3,4],[2,4],[1,2],[2,3],[5,6]]Interactive diagram
Disjoint-set forest after all unions
union 1-3, 3-4, 2-4, 1-2, 2-3Roads merged {1,2,3,4} under one root and {5,6} under another — two components of size 4 and 2.
12C · Code with teaching comments
D · Why it works and complexityO(E · α(N)) timeO(N) spaceOpen the invariant and proofExplore the proofHide the proof
Union-Find is the reflex whenever a problem is purely about connectivity with no need for distances or paths. It turns the graph into a near-constant-time 'same group?' oracle, leaving only the O(1)-per-component cost formula to finish the job.
ComplexityO(E · α(N)) time, O(N) space — Each union and find is O(α(N)) — the inverse Ackermann function, effectively constant. E roads give O(E · α(N)); the final sweep over N cities to read sizes is O(N · α(N)). Space is O(N) for the parent and size arrays.
E · Edge cases and pitfalls7n = 1, c_lib = 5, c_road = 3, roads = [] — One component of size 1: min(5, 5 + 0) = 5. A lone city always just buys a library.Explore edge casesHide edge cases
Edge cases handled
n = 1, c_lib = 5, c_road = 3, roads = []One component of size 1: min(5, 5 + 0) = 5. A lone city always just buys a library.roads = [[1,2],[1,2]] (duplicate)Second union finds equal roots → no-op. Duplicates and self-loops never add cost.c_lib = c_roadmin(s·c, c + (s−1)·c) = s·c either way — the shortcut returns n·c_lib, still correct.
Pitfalls
- Dropping the per-component library — a component of size s with only roads has no library to reach.
- Recursive find on a long chain — risks stack overflow; the iterative while loop avoids it.
- Accumulating the total in 32-bit int — n × c_lib can reach 10¹⁰, well past int range.
- Counting size off non-root nodes — only the root carries the correct component size.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic long roadsAndLibraries(int n, int cLib, int cRoad, int[][] roads) { if (cLib <= cRoad) { return (long) n * cLib; // libraries never lose — skip roads entirely } int[] parent = new int[n + 1];Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Skipping the mandatory per-component library
When roads are cheaper, candidates sometimes price a component as (s − 1) × c_road and forget the one library that makes the roads worth anything. Without a library, a connected component still has zero access.
Explore the code comparisonHide the code comparison
Skipping the mandatory per-component library
When roads are cheaper, candidates sometimes price a component as (s − 1) × c_road and forget the one library that makes the roads worth anything. Without a library, a connected component still has zero access.
Don't write this
# roads cheaper, but no library bought
total += (size - 1) * c_roadWrite this instead
# one library anchors the component, roads reach the rest
total += c_lib + (size - 1) * c_road232-bit overflow on the total
With n up to 10⁵ and costs up to 10⁵, a single component can cost on the order of 10¹⁰, which silently wraps around in a 32-bit int and yields a negative or tiny answer.
Explore the code comparisonHide the code comparison
32-bit overflow on the total
With n up to 10⁵ and costs up to 10⁵, a single component can cost on the order of 10¹⁰, which silently wraps around in a 32-bit int and yields a negative or tiny answer.
Don't write this
int total = 0;
total += size * cLib; // size * cLib overflows intWrite this instead
long total = 0;
total += (long) size * cLib; // promote before multiplying3Recursive find / DFS blowing the stack
A path of 10⁵ cities forms a chain that deep. Recursive find or DFS nests one frame per node and overflows the call stack; an explicit loop keeps the depth in the heap.
Explore the code comparisonHide the code comparison
Recursive find / DFS blowing the stack
A path of 10⁵ cities forms a chain that deep. Recursive find or DFS nests one frame per node and overflows the call stack; an explicit loop keeps the depth in the heap.
Don't write this
def find(x):
if parent[x] == x:
return x
return find(parent[x]) # recursion depth = chain lengthWrite this instead
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return xRevise
Carry the pattern into the interview
Pattern to remember
Connected components + per-component greedy choice
Code pattern to memorize
components = union-find over roads
for each component of size s:
cost += min(s * c_lib, # a library in every city
c_lib + (s - 1) * c_road) # one library, span with roads
answer = sum of per-component costsRules to remember
- 1Every component needs at least one library — a city with no library can never reach one through roads alone.
- 2If
c_lib ≤ c_road, libraries are never worse than roads — the answer is justn * c_lib. State this special case first. - 3Spanning a component of
scities needs exactlys − 1roads (a spanning tree); extra roads are wasted money. - 4Decide each component independently — roads only connect cities already in the same component, so components never share cost.
- 5Use
longfor the running total:nup to 10⁵ times costs up to 10⁵ overflows 32-bitint.
Use this pattern when
- Cost is split between a 'node cost' and an 'edge cost' and you must cover every node
- The graph breaks into independent pieces you can price separately
- You only need connectivity (reach a facility), not actual shortest distances
- The decision per group is a simple two-way greedy comparison
Choose another pattern when
- You need the actual shortest path between two cities → BFS / Dijkstra, not components
- Edges carry differing weights you must minimize over → that is an MST (Kruskal/Prim), not this flat comparison
- You must keep the graph connected as a whole → spanning-tree problem, not per-component
- Libraries have per-city differing costs → this uniform-cost greedy no longer applies
Interview takeaway
When a problem splits cleanly into independent groups, price each group on its own and add the results. Here the only graph work is 'which cities are connected', which Union-Find answers in near-constant time per edge — the rest is a one-line cost comparison.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternConnected Components + Independent Per-Group Greedy. Recognize it when a graph splits into pieces you can price separately and each piece reduces to a small comparison. Union-Find supplies the component sizes in near-constant time per edge; the same skeleton appears in Components in a Graph, Number of Provinces, and Journey to the Moon.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Number of Connected ComponentsThe pure component-count version of this problem — Union-Find with no cost layer on top.
- Components in a GraphReport the smallest and largest component sizes instead of a cost — same Union-Find sizing.
- Kruskal's Minimum Spanning TreeWhen roads carry differing weights, choosing which to repair becomes an MST, built with the same Union-Find.
- Journey to the MoonAnother Union-Find component-size problem, but counting cross-component pairs rather than minimizing cost.