Can the graph be split in two?
An undirected graph has n nodes numbered 0 to n − 1 and a list of
edges. It may be disconnected. Return true if its nodes can be split into two sets so that
every edge joins a node in one set to a node in the other, and false otherwise.
Examples
| Input | Output | Why |
|---|---|---|
n = 4, edges = [[0,1],[1,2],[2,3],[3,0]] | true | A square: {0, 2} and {1, 3}. |
n = 3, edges = [[0,1],[1,2],[2,0]] | false | A triangle is an odd cycle. |
n = 6, edges = [[0,1],[2,3],[3,4],[4,2]] | false | The first part is fine; the triangle 2–3–4 is not. |
Constraints
- 1 ≤ n ≤ 10⁴
- 0 ≤ edges.length ≤ 2 · 10⁴
- No self-loops or repeated edges
Hints
Hint 1
Colour the first node A. Its neighbours are then forced to be B, their neighbours A, and so on.
Hint 2
Run that forced colouring as a breadth-first search. If an edge ever joins two nodes of the same colour, return false.
Hint 3
The graph may be disconnected: start a new search from every node that is still uncoloured.
Where people lose marks
- Checking only the piece that contains node 0.
- Treating "visited" and "colour" separately and forgetting to compare colours on edges to visited nodes.
- Using recursion on a long path of 10⁴ nodes in Python.
Dry run on 6,[[0,1],[1,2],[0,3],[1,4],[3,4],[2,5],[4,5],[0,4]]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 3
0 is uncoloured: give it colour A and start a breadth-first search.
-
Step 2 / 3
From 0: its uncoloured neighbours 1, 3, 4 get the opposite colour, B.
-
Step 3 / 3
1 and 4 are joined but already have the same colour. The graph contains an odd cycle and cannot be two-coloured.
How to think about it
Within one connected piece, the colour of the first node fixes every other colour: a neighbour of an A node must be B, and so on. So there is nothing to choose, only something to check. Colour the piece by breadth-first search, giving each newly reached node the opposite of the node it was reached from. An edge between two nodes of the same colour proves that no colouring exists (the graph contains an odd cycle); if no such edge appears, the colouring is valid.
Repeat from every uncoloured node so that each piece is checked.
Complexity. Time O(n + m) · Space O(n + m)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import deque
def is_bipartite(n, edges):
adj = [[] for _ in range(n)]
for a, b in edges:
adj[a].append(b)
adj[b].append(a)
colour = [-1] * n # -1 uncoloured, otherwise 0 or 1
for s in range(n):
if colour[s] != -1:
continue
colour[s] = 0 # a new piece: its first node is free
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if colour[v] == -1:
colour[v] = 1 - colour[u] # forced
q.append(v)
elif colour[v] == colour[u]:
return False # an edge inside one colour: odd cycle
return True#include <queue>
#include <vector>
bool solve(int n, const std::vector<std::vector<int>>& edges) {
std::vector<std::vector<int>> adj(n);
for (const auto& e : edges) { adj[e[0]].push_back(e[1]); adj[e[1]].push_back(e[0]); }
std::vector<int> colour(n, -1);
for (int s = 0; s < n; ++s) {
if (colour[s] != -1) continue;
colour[s] = 0;
std::queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : adj[u]) {
if (colour[v] == -1) { colour[v] = 1 - colour[u]; q.push(v); } // forced
else if (colour[v] == colour[u]) return false; // odd cycle
}
}
}
return true;
}import java.util.*;
class Solution {
public boolean isBipartite(int n, int[][] edges) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) { adj.get(e[0]).add(e[1]); adj.get(e[1]).add(e[0]); }
int[] colour = new int[n];
Arrays.fill(colour, -1);
for (int s = 0; s < n; s++) {
if (colour[s] != -1) continue;
colour[s] = 0;
ArrayDeque<Integer> q = new ArrayDeque<>();
q.add(s);
while (!q.isEmpty()) {
int u = q.poll();
for (int v : adj.get(u)) {
if (colour[v] == -1) { colour[v] = 1 - colour[u]; q.add(v); } // forced
else if (colour[v] == colour[u]) return false; // odd cycle
}
}
}
return true;
}
}function isBipartite(n, edges) {
const adj = Array.from({ length: n }, () => []);
for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); }
const colour = new Array(n).fill(-1);
for (let s = 0; s < n; s++) {
if (colour[s] !== -1) continue;
colour[s] = 0;
const q = [s];
for (let head = 0; head < q.length; head++) {
const u = q[head];
for (const v of adj[u]) {
if (colour[v] === -1) { colour[v] = 1 - colour[u]; q.push(v); } // forced colour
else if (colour[v] === colour[u]) return false; // odd cycle
}
}
}
return true;
}use std::collections::VecDeque;
fn solve(n: i64, edges: &[[i64; 2]]) -> bool {
let n = n as usize;
let mut adj = vec![Vec::new(); n];
for e in edges {
let (a, b) = (e[0] as usize, e[1] as usize);
adj[a].push(b);
adj[b].push(a);
}
let mut colour: Vec<Option<u8>> = vec![None; n];
for s in 0..n {
if colour[s].is_some() {
continue;
}
colour[s] = Some(0);
let mut q = VecDeque::from([s]);
while let Some(u) = q.pop_front() {
let cu = colour[u].unwrap();
for &v in &adj[u] {
match colour[v] {
None => {
colour[v] = Some(1 - cu); // forced
q.push_back(v);
}
Some(cv) if cv == cu => return false, // odd cycle
_ => {}
}
}
}
}
true
}import qualified Data.Map.Strict as M
solve :: Int -> [[Int]] -> Bool
solve n edges = go [0 .. n - 1] M.empty
where
adj = M.fromListWith (++) (concat [[(a, [b]), (b, [a])] | [a, b] <- edges])
go [] _ = True
go (s : rest) col
| M.member s col = go rest col
| otherwise = case paint [s] (M.insert s False col) of
Nothing -> False
Just col' -> go rest col'
-- breadth-first colouring; Nothing as soon as an edge joins equal colours
paint [] col = Just col
paint (u : q) col = step (M.findWithDefault [] u adj) q col
where
cu = col M.! u
step [] q' c = paint q' c
step (v : vs) q' c = case M.lookup v c of
Nothing -> step vs (q' ++ [v]) (M.insert v (not cu) c)
Just cv | cv == cu -> Nothing
| otherwise -> step vs q' c
Variants to try
- Return the two sets, or the smaller of them.
- If the graph is not bipartite, return an odd cycle as evidence.
- Each piece may be flipped independently: how many valid colourings are there? (2 to the number of pieces.)
Next problems
Split people who dislike each other
Put n people into two rooms so that no two people who dislike each other share one. A dislike is an edge, and the rooms are the two colours.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem →Write a solution and run it against the real test table.
Keep going
Split people who dislike each other
Put n people into two rooms so that no two people who dislike each other share one. A dislike is an edge, and the rooms are the two colours.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.