Skip to the chapter
←DSAGGroundworkΣDSA
DSA/Intermediate/I7
19 / 48 written chapters
/

Levels and plan
  1. B1Complexity analysis
  2. B2JavaScript toolkit
  3. B3Arrays & strings
  4. B4Prefix sums
  5. B5Hashing
  6. B6Two pointers
  7. B7Sliding window
  8. B8Binary search
  9. B9Basic recursion
  10. B10Sorting algorithms
  11. B11Stacks & queues
  12. B12Linked lists
  1. I1Trees
  2. I2BST operations
  3. I3Tree problems in depth
  4. I4Heaps & priority queues
  5. I5Graphs: representation
  6. I6BFS on grids
  7. I7Graph problems
  8. I8Topological patterns
  9. I9Backtracking
  10. I10DP: 1D
  11. I11DP: 2D
  12. I12DP state machines
  13. I13Greedy algorithms
  14. I14Intervals
  15. I15Bit manipulation
  16. I16Matrix problems
  17. I17Tries
  18. I18Monotonic stack & queue
  19. I19Math for interviews
  20. I20Design & simulation
  21. I21Divide and conquer
  22. I22Randomization
  1. A1Advanced DP
  2. A2Union-Find
  3. A3Advanced graph algorithms
  4. A4Minimum Spanning Tree
  5. A5Advanced graphs
  6. A6Segment & Fenwick trees
  7. A7Sparse tables
  8. A8String algorithms
  9. A9Design problems
  10. A10Advanced backtracking
  11. A11Games & probability DP
  12. A12Ordered structures
  13. A13Sweep line & geometry
  14. A14Interview strategy

ΣI7 · Part 2 · Intermediate

Graph problems

Components, cycles, colouring, trees and reachability: the same traversal each time, with a different thing to track.

20 min read2 diagrams11 sections15 exercises
19 of 48

In this chapter

  • Connected components — how many separate "islands" exist
  • Number of provinces: when the graph arrives as a matrix
  • Cycle detection — the rule differs by directed vs undirected
  • Bipartite check — can you 2-color it with no clashes?
  • Graph valid tree: connected, with no cycle

and 6 more

about 20 min
11On this page
  1. Connected components — how many separate "islands" exist
  2. Number of provinces: when the graph arrives as a matrix
  3. Cycle detection — the rule differs by directed vs undirected
  4. Bipartite check — can you 2-color it with no clashes?
  5. Graph valid tree: connected, with no cycle
  6. Edges arriving one at a time: the union-find pointer
  7. Reachability: what can I get to from here
  8. Weighted DFS: evaluate division
  9. Clone graph, with a queue
  10. Grids, word ladder and other state-space searches
  11. Recognizing which one an unseen problem wants

Connected components — how many separate "islands" exist

component 1 (3 nodes) component 2 (2 nodes) no edges between the two groups
Run DFS/BFS from any unvisited node — everything it reaches is one component. Repeat until nothing's left unvisited.
function countComponents(n, edges) {
  const graph = Array.from({ length: n }, () => []);
  for (const [u, v] of edges) { graph[u].push(v); graph[v].push(u); }

  const visited = new Set();
  let components = 0;

  for (let node = 0; node < n; node++) {
    if (visited.has(node)) continue; // already covered by an earlier DFS
    components++;
    // flood-fill everything reachable from "node" into "visited"
    const stack = [node];
    visited.add(node);
    while (stack.length) {
      const curr = stack.pop();
      for (const next of graph[curr]) {
        if (!visited.has(next)) { visited.add(next); stack.push(next); }
      }
    }
  }
  return components;
}

The pattern generalizes directly to grid problems ("number of islands"): each land cell is a node, each adjacent land cell is an edge — same flood-fill, just walking up/down/left/right instead of an adjacency list.

Number of provinces: when the graph arrives as a matrix

The flood-fill loop above assumed an adjacency list. A common variant hands you the graph as an adjacency matrix instead: n cities, and isConnected[i][j] === 1 when city i and city j are directly connected. A province is a group of cities joined directly or through other cities, which is exactly a connected component. The answer is the number of components.

Nothing new is needed except one change: there is no neighbour list to iterate, so the neighbours of city c are the columns in row c that hold a 1. Scanning a row costs O(n), and every city is scanned once, so this version takes O(n²) time. That is not a weakness of the code: with a matrix, O(n²) is simply the cost of reading the input.

function findCircleNum(isConnected) {
  const n = isConnected.length;
  const visited = new Array(n).fill(false);
  let provinces = 0;
  for (let city = 0; city < n; city++) {
    if (visited[city]) continue; // an earlier search already reached it
    provinces++;
    visited[city] = true;
    const stack = [city];
    while (stack.length > 0) {
      const current = stack.pop();
      for (let other = 0; other < n; other++) { // a row of the matrix is the neighbour list
        if (isConnected[current][other] === 1 && !visited[other]) {
          visited[other] = true;
          stack.push(other);
        }
      }
    }
  }
  return provinces;
}

Dry run: isConnected = [[1, 1, 0], [1, 1, 0], [0, 0, 1]]

cityvisited before?row scannednewly visitedprovinces
0norow 0: columns 0 and 1 hold a 1; 0 is visited already11
(stack)—row 1: columns 0 and 1 hold a 1, both visited—1
1yesskipped—1
2norow 2: only column 2, which is itself—2

The answer is 2: cities 0 and 1 form one province, city 2 another. Three slips are common. The diagonal is 1 by convention (a city is connected to itself), so a city must be marked visited before its row is read, or it can be treated as its own unvisited neighbour. The matrix is symmetric, but do not read only half of it in a traversal: with edges 0-2 and 1-2, a search that looks only at columns after the current row cannot get from city 2 back to city 1, and counts two provinces instead of one. And there is no need to translate the matrix into an adjacency list first, since that costs another O(n²) and gives nothing back. We checked this against a union-find count on thousands of random symmetric matrices.

Cycle detection — the rule differs by directed vs undirected

⚠ The single most common graph-interview mistake On an undirected graph, seeing a visited neighbor doesn't automatically mean a cycle — it might just be the edge you arrived from. You must track and exclude the parent explicitly. On a directed graph, that concern doesn't apply, but you now need to distinguish "visited earlier, finished" from "visited and still on the current path" — the difference between them is the whole check.
// undirected: skip the edge back to where you just came from
function hasCycleUndirected(graph, node, visited, parent) {
  visited.add(node);
  for (const next of graph[node]) {
    if (!visited.has(next)) {
      if (hasCycleUndirected(graph, next, visited, node)) return true;
    } else if (next !== parent) {
      return true; // hit an already-visited node that ISN'T where we came from
    }
  }
  return false;
}

// directed: need a THIRD state — "on the current recursion path"
function hasCycleDirected(graph, n) {
  const state = new Array(n).fill(0); // 0=unvisited, 1=in-progress, 2=done

  function dfs(node) {
    state[node] = 1;
    for (const next of graph[node]) {
      if (state[next] === 1) return true;       // back-edge to an in-progress node = cycle
      if (state[next] === 0 && dfs(next)) return true;
    }
    state[node] = 2;
    return false;
  }

  for (let i = 0; i < n; i++) {
    if (state[i] === 0 && dfs(i)) return true;
  }
  return false;
}

That three-state trick (unvisited / in-progress / done) on a directed graph is the exact same idea behind detecting a circular dependency — "in-progress" means "currently on the stack of things depending on each other," and looping back to one of those is the cycle.

Dry run: hasCycleDirected on the 3-node cycle 0→1→2→0

Callstate[] beforeNeighbor checkedNeighbor's stateResult
dfs(0)[0, 0, 0] → mark 0 in-progress: [1, 0, 0]10 (unvisited) → recurse into dfs(1)—
dfs(1)[1, 0, 0] → mark 1 in-progress: [1, 1, 0]20 (unvisited) → recurse into dfs(2)—
dfs(2)[1, 1, 0] → mark 2 in-progress: [1, 1, 1]01 (in-progress!)return true — cycle found

Node 0 never reaches state 2 ("done") — the call stack is still three deep inside it when the back-edge to it is found. That's exactly what "in-progress" is for: if 0 had already finished (state 2) before 2 ever looked at it, seeing it again would mean a second, unrelated path merged back in — not a cycle. Only a back-edge to something still open on the current path counts.

Bipartite check — can you 2-color it with no clashes?

every red only connects to green — never red-to-red
Bipartite = every edge crosses between the two groups, never stays within one.
function isBipartite(graph) {
  const color = new Array(graph.length).fill(0); // 0=uncolored, 1 or -1 = the two colors

  for (let start = 0; start < graph.length; start++) {
    if (color[start] !== 0) continue;
    color[start] = 1;
    const queue = [start];

    while (queue.length) {
      const node = queue.shift();
      for (const next of graph[node]) {
        if (color[next] === 0) {
          color[next] = -color[node]; // force the opposite color
          queue.push(next);
        } else if (color[next] === color[node]) {
          return false; // a neighbor shares my color — contradiction
        }
      }
    }
  }
  return true;
}
Say it like this → "I'll BFS while alternating colors between each node and its neighbors — if I ever find an edge connecting two same-colored nodes, that's a direct proof the graph isn't 2-colorable, which is exactly what 'not bipartite' means."

Graph valid tree: connected, with no cycle

Given n nodes and a list of undirected edges, decide whether the graph is a tree: every node reachable from every other (one component) and no cycle. You already have both tools, a component count and a cycle check. But there is a shorter route, because a tree has a numeric fingerprint.

Start with a single node and no edges: connected, no cycle. Adding a node that attaches by one edge keeps it connected and cycle-free, so a tree with n nodes built this way has n - 1 edges. Any extra edge between two nodes already in the tree closes a cycle, and any missing edge leaves a node unreachable. So for a graph with n nodes and exactly n - 1 edges, "no cycle" and "connected" are the same statement: if it is connected, it cannot have a cycle, and if it has no cycle, it must be connected. Checking one of the two is enough, once the edge count is right.

function validTree(n, edges) {
  if (edges.length !== n - 1) return false; // a tree on n nodes has exactly n - 1 edges
  const graph = Array.from({ length: n }, () => []);
  for (const [u, v] of edges) {
    graph[u].push(v);
    graph[v].push(u);
  }
  const seen = new Set([0]);
  const stack = [0];
  while (stack.length > 0) {
    const node = stack.pop();
    for (const next of graph[node]) {
      if (!seen.has(next)) {
        seen.add(next);
        stack.push(next);
      }
    }
  }
  return seen.size === n; // n - 1 edges and connected leaves no room for a cycle
}

Dry run: two inputs with n = 5 and four edges

edgesedge count = n - 1?nodes reached from 0result
[[0,1], [0,2], [0,3], [1,4]]4 = 4, yes0, 3, 2, 1, 4: five of fivetrue
[[0,1], [1,2], [2,3], [1,3]]4 = 4, yes0, 1, 3, 2: four of five, node 4 is never reachedfalse

The second input passes the count and fails the traversal: it has a cycle (1, 2, 3) and an isolated node (4), and the extra edge that made the cycle also took the place of the one that would have connected node 4. Both checks are needed, which is why the function does both. Its time is O(V + E), and, since the count test returns first, the traversal only runs when E = V - 1.

⚠ Edge cases that decide this one Duplicate edges. n = 3, edges = [[0,1], [0,1]] has n - 1 = 2 edges but node 2 is unreachable, so the traversal rejects it. Self-loops. [[0,0]] with n = 2 has the right count but a loop, and node 1 is unreachable. One node, no edges. That is a tree (the count is 0 = 1 - 1, and one node is reached). The solution above assumes n is at least 1. And the common wrong shortcut, "connected, so return true" without the edge count, accepts a cycle with extra edges. We checked both versions of this function against an independent union-find test on thousands of random graphs, including all of those shapes.
Say it like this → "A tree on n nodes has n - 1 edges, and with that many edges, being connected and having no cycle mean the same thing. So I'll reject any other edge count at once, then run one traversal from node 0 and check that it reaches all n nodes."

Edges arriving one at a time: the union-find pointer

Traversal answers questions about a graph you already hold. When the edges come one at a time and you must answer after each ("are these two already connected?", "did this edge create a cycle?", "how many groups now?"), re-running a DFS after every edge costs O(V + E) each time. A data structure built for exactly this is union-find (disjoint sets). It keeps each group as a tree of parent pointers and gives every group a single root that names it: find(x) follows the pointers up to the root, and union(a, b) joins the two groups by pointing one root at the other. Two nodes are connected exactly when their roots are equal.

That makes the cycle test almost free. An edge [u, v] closes a cycle exactly when u and v already share a root, because a path between them exists before the edge is added.

function validTreeUnionFind(n, edges) {
  if (edges.length !== n - 1) return false;
  const parent = Array.from({ length: n }, (_, i) => i);
  const find = (x) => {
    while (parent[x] !== x) {
      parent[x] = parent[parent[x]]; // path halving: point at the grandparent as we climb
      x = parent[x];
    }
    return x;
  };
  for (const [u, v] of edges) {
    const rootU = find(u), rootV = find(v);
    if (rootU === rootV) return false; // already connected: this edge would close a cycle
    parent[rootU] = rootV; // merge the two groups
  }
  return true;
}

This is the same answer as the traversal version: with n - 1 edges and no edge closing a cycle, the n - 1 successful merges have joined all n nodes into one group. The line parent[x] = parent[parent[x]] (path halving) keeps the trees flat, so each find is, for practical purposes, constant time. The full structure, with union by size, the amortized cost and the harder variants (weighted ratios, parity, offline deletion) has its own chapter, Union-find.

The questionReach forWhy
Count components of a fixed graphDFS or BFS in a loopone pass, O(V + E), simplest code
Does this edge close a cycle (undirected), edges arriving in orderunion-findanswers each edge in nearly constant time
Components after each of many added edgesunion-finddecrement a counter on each successful merge
Anything directed: cycles, order, reachabilityDFS with three states, or topological sortunion-find ignores direction
Shortest path, or the path itselfBFS (Dijkstra if weighted)union-find cannot tell you routes

Reachability: what can I get to from here

"Reachable" questions ask which nodes a traversal from a start can touch. Nothing in the traversal changes. What changes is the question you ask of the visited set at the end, and sometimes whether you may keep it at all.

Keys and rooms: is everything reachable?

There are n rooms, numbered from 0. Room 0 is unlocked. Each room holds a list of keys, and key k opens room k. Can you enter every room? That is a directed graph (the keys in room i are edges i → k), and the question is whether the search from node 0 reaches all n nodes.

function canVisitAllRooms(rooms) {
  const opened = new Set([0]); // room 0 is unlocked
  const stack = [0];
  while (stack.length > 0) {
    for (const key of rooms[stack.pop()]) {
      if (!opened.has(key)) {
        opened.add(key);
        stack.push(key);
      }
    }
  }
  return opened.size === rooms.length;
}

Dry run: rooms = [[1, 3], [3, 0, 1], [2], [0]]

PopKeys in that roomNew rooms openedopened after
start——{0}
01, 31, 3{0, 1, 3}
30none (0 is open){0, 1, 3}
13, 0, 1none{0, 1, 3}

The stack empties with three rooms open out of four, so the answer is false. Room 2 holds the only key to room 2 itself, so no room that can be entered gives you a way in. Time O(n + k) for k keys in total. Do not forget that a key can lead to a room you have already opened: without the opened set a loop of keys would run forever.

All paths from source to target

Now you want every route, not whether one exists. In a directed acyclic graph with nodes 0 to n - 1, list every path from node 0 to node n - 1. A path can share nodes with another, so a global visited set would wrongly block the second route to a node. Instead keep the current path, extend it before each recursive call, and take the last node off after the call returns. This is the same choose, explore, un-choose rhythm as backtracking, and in a DAG it needs no visited marking at all, since a path can never come back to a node it has used.

function allPathsSourceTarget(graph) {
  const target = graph.length - 1;
  const paths = [];
  const path = [0];
  function dfs(node) {
    if (node === target) {
      paths.push([...path]); // copy: path keeps changing after this
      return;
    }
    for (const next of graph[node]) {
      path.push(next); // choose
      dfs(next);
      path.pop(); // un-choose
    }
  }
  dfs(0);
  return paths;
}

Dry run: graph = [[1, 2], [3], [3], []]

Callpath while insideWhat happens
dfs(0)[0]try the neighbour 1
dfs(1)[0, 1]try the neighbour 3
dfs(3)[0, 1, 3]this is the target: record a copy [0, 1, 3], return
back in dfs(0)[0]after un-choosing 3 and 1, try the neighbour 2
dfs(2) then dfs(3)[0, 2, 3]record a copy [0, 2, 3]

The result is [[0, 1, 3], [0, 2, 3]]. Two traps. Pushing path itself instead of [...path] stores the same array each time, and since it is emptied again by the un-choose steps you end up with a list of arrays that are all just [0]. And the cost is not O(V + E): the answer itself can be huge. If every node points to every later node, there are 2n-2 paths (we ran it for n = 4, 6 and 10 and counted 4, 16 and 256), so the time grows with the total length of the output (plus any dead-end branches the search explores), and you should say that in an interview. If the graph can have cycles, the current path has to be tracked as a set so that a path does not revisit its own nodes.

A shortcut worth knowing: where must you start?

Suppose you may start from any nodes you like in a directed acyclic graph, and want the fewest starting nodes from which every node is reachable. A node with no incoming edge cannot be reached from anywhere, so it has to be a start itself. Every other node has an incoming edge and, going backwards along incoming edges, the walk cannot go on for ever in an acyclic graph, so it ends at a node without incoming edges: the nodes you already chose. So the answer is exactly the nodes with in-degree zero. No traversal is needed.

function findSmallestSetOfVertices(n, edges) {
  const hasIncoming = new Array(n).fill(false);
  for (const [, to] of edges) hasIncoming[to] = true;
  const result = [];
  for (let i = 0; i < n; i++) if (!hasIncoming[i]) result.push(i);
  return result;
}

O(V + E) time. The argument leans on the graph being acyclic. In a graph that has a cycle, a group of nodes can feed each other and still have no outside edge into it, and the in-degree test would miss it. We checked the function on random DAGs: its set reaches every node, and removing any node from the set leaves that node unreachable.

Weighted DFS: evaluate division

You get equations such as a / b = 2.0 and b / c = 3.0, and queries such as a / c. For each query, return the value, or -1 if it cannot be worked out. The trick is to see the variables as nodes and each equation as a weighted edge. a / b = 2 is an edge from a to b with weight 2, and it also tells you b / a = 1 / 2, so it is an edge back with weight 0.5, a directed pair for every equation. A query x / y asks for the product of the weights along a path from x to y, because x / y = (x / m) · (m / y) for any variable m in between. The chain a / b · b / c cancels the b and leaves a / c.

That is a graph search that carries a number along. Instead of the visited stack of plain nodes, push [node, product so far], where the product so far is start / node. When the search arrives at the target, the product is the answer.

function calcEquation(equations, values, queries) {
  const graph = new Map();
  const link = (a, b, ratio) => {
    if (!graph.has(a)) graph.set(a, []);
    graph.get(a).push([b, ratio]);
  };
  equations.forEach(([a, b], i) => {
    link(a, b, values[i]); // a / b = value
    link(b, a, 1 / values[i]); // so b / a = 1 / value
  });

  const evaluate = (from, to) => {
    if (!graph.has(from) || !graph.has(to)) return -1; // an unknown variable
    const seen = new Set([from]);
    const stack = [[from, 1]]; // [variable, from / variable]
    while (stack.length > 0) {
      const [node, product] = stack.pop();
      if (node === to) return product;
      for (const [next, ratio] of graph.get(node)) {
        if (!seen.has(next)) {
          seen.add(next);
          stack.push([next, product * ratio]); // from/next = (from/node) * (node/next)
        }
      }
    }
    return -1; // no chain of equations links them
  };

  return queries.map(([from, to]) => evaluate(from, to));
}

Dry run: a / b = 2, b / c = 3, then six queries

QueryWhat the search doesAnswer
a / cfrom a (product 1) to b (1 · 2 = 2), then to c (2 · 3 = 6)6
b / afrom b to a along the reverse edge, weight 1/20.5
c / ac to b (1/3), then b to a (1/3 · 1/2)0.16666666666666666
a / ee is not in any equation-1
a / athe start is the target at once, product 11
x / xx is not in any equation, so it is unknown even over itself-1

We ran the function and got exactly the answers above, and compared it with the true ratios on thousands of random consistent systems. Cost: one traversal per query, O(V + E) each, so O(Q · (V + E)) for Q queries. If there are many queries, a weighted union-find that stores each node's ratio to its root answers each one in near-constant time after the equations are merged; that version is in Union-find.

⚠ Pitfalls in evaluate division The reverse edge. Without b / a = 1 / v you cannot answer c / a. Unknown variable versus same variable. a / a is 1 only if a appears in some equation; the check for unknown variables comes first. A fresh visited set per query. Reusing one set across queries makes later queries fail. Floating point. Products of ratios are doubles, so compare answers with a tolerance in your own tests (c / a above is 0.16666666666666666, not exactly one sixth). The problem guarantees the equations do not contradict each other, which is why the first path you find is as good as any other.

Clone graph, with a queue

The copy-with-a-map solution to clone graph is in Graphs: representation and traversal, written as recursion. Here is the same idea as BFS, which is the right choice when the graph can be deep, since a queue has no call-stack limit. The shape is a traversal where, on first sight of a neighbour, you also create and register its copy, and for every neighbour edge you link the copies. It uses GraphNode from that chapter.

function cloneGraphBfs(node) {
  if (node === null) return null;
  const copies = new Map([[node, new GraphNode(node.val)]]);
  const queue = [node];
  for (let head = 0; head < queue.length; head++) {
    const original = queue[head];
    for (const neighbor of original.neighbors) {
      if (!copies.has(neighbor)) {
        copies.set(neighbor, new GraphNode(neighbor.val)); // create and register on first sight
        queue.push(neighbor);
      }
      copies.get(original).neighbors.push(copies.get(neighbor));
    }
  }
  return copies.get(node);
}

The line that links the two copies sits outside the if, because an edge to an already-seen neighbour still has to be copied; only the creation of the copy happens once. Each node is created once and each edge is copied from both ends, so it is O(V + E) with O(V) space. This was checked on thousands of random graphs, with self-loops: the copy had the same shape and shared no node object with the original. The reverse mistake is to link the copies only when the neighbour is new, which loses every edge that leads back to a node already discovered.

Grids, word ladder and other state-space searches

Several of the best-known "graph" problems do not appear on this page, on purpose. Number of islands and its relatives, the multi-source spread of rotting oranges, the shortest path in a binary matrix, and the "which cells can reach both oceans" problem (reach from the border instead of towards it) all run on a grid where the neighbours are computed rather than stored. Word ladder, and the other problems where the nodes are states such as a word or a lock combination and the edges are one legal move, share the same shape. They are all one idea, BFS over an implicit graph, and they live in BFS on grids and implicit graphs. The components, cycles, colouring, trees and reachability on this page work on graphs you build from an edge list or a matrix, and they use the traversals from the previous chapter unchanged.

Recognizing which one an unseen problem wants

  • "How many groups/islands/provinces" → connected components
  • "Can these all be completed" / "is there a circular dependency" → cycle detection (directed, usually — think course prerequisites)
  • "Can you split into two groups with no conflicts" / "is this graph 2-colorable" → bipartite check
  • "Is this a tree" / "is it connected with no cycle" → count the edges first (n - 1), then check connectivity
  • "Can I reach everything / every room / which nodes must be starts" → one traversal and a look at the visited set, or the in-degrees for a DAG
  • "List every path" → DFS that adds a node before the call and removes it after, copying the path when it ends
  • Ratios, exchange rates, "A relates to B by a factor" → weighted edges in both directions and a product along the path
  • Edges arriving one at a time with "are they connected yet" → union-find instead of a fresh traversal
  • Grids, word ladder and other implicit graphs → BFS on grids and implicit graphs
  • All of these reuse the exact same BFS/DFS skeleton from the previous chapter — the only new part is what you track while visiting
Before you move on
  • Count connected components with a loop over every node plus a flood-fill, and connect that shape to "number of islands" on a grid.
  • Explain why undirected cycle detection needs to exclude the parent, but directed cycle detection needs a third "in-progress" state instead.
  • Trace the three-state cycle check on a graph with no cycle, and confirm every node reaches state 2 without a false positive.
  • Write isBipartite with BFS colouring, and say in one sentence what a same-colored edge proves.
  • Given a new problem, decide which of the three (components, cycle, bipartite) it is before writing any code.
  • Count provinces from an adjacency matrix and say why that version is O(n²).
  • Explain why n - 1 edges plus connectivity is enough for a valid tree, and give an input that passes the count but fails the traversal.
  • Say when you would use union-find instead of a traversal, and when you could not.
  • Solve keys and rooms, list all paths in a DAG (and say why the cost depends on the output), and name the in-degree shortcut for the smallest set of start nodes.
  • Model evaluate division as a weighted graph with reverse edges, and handle the unknown-variable and same-variable cases.
  • Write clone graph with a queue and explain why the link to the copy sits outside the "first time seen" branch.

15Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsPacific Atlantic Water Flowintermediate
  • 8 testsWord Ladderadvanced
  • 9 testsIs Graph Bipartite?intermediate
  • 9 testsShortest Path in a Binary Matrixintermediate
  • 8 testsKeys and Roomsintermediate
  • 8 testsNumber of Connected Components in an Undirected Graphintermediate
  • 8 testsCourse Scheduleintermediate
  • 8 testsClone Graphintermediate
  • 8 testsRedundant Connectionadvanced
  • 10 testsGraph Valid Treeintermediate
  • 9 testsEvaluate Divisionadvanced
  • 8 testsNumber of Provincesintermediate
  • 8 testsAll Paths From Source to Targetintermediate
  • 9 testsPossible Bipartitionintermediate
  • 8 testsSmallest Set of Vertices to Reach All Nodesintermediate
All 318 for this level →

✓Check yourself

5 questions, pass at 4. The check runs in your browser and the result stays on this device, so it is a self-check and not a certificate.

I7

Finished reading?

0 of 48 chapters read in this series

Mark as read

→Next stop

  1. I6Previous · I6BFS on grids and implicit graphs
  2. I7you are here
  3. Next · I8Topological patternsAny problem phrased as "X must come before Y" is a DAG asking to be linearized.29 min read

01On this page

11 sections · 0%

  1. 01Connected components — how many separate "islands" exist
  2. 02Number of provinces: when the graph arrives as a matrix
  3. 03Cycle detection — the rule differs by directed vs undirected
  4. 04Bipartite check — can you 2-color it with no clashes?
  5. 05Graph valid tree: connected, with no cycle
  6. 06Edges arriving one at a time: the union-find pointer
  7. 07Reachability: what can I get to from here
  8. 08Weighted DFS: evaluate division
  9. 09Clone graph, with a queue
  10. 10Grids, word ladder and other state-space searches
  11. 11Recognizing which one an unseen problem wants
Mark as read

[] chapters · t top