Split people who dislike each other
There are n people, labelled 1 to n. Each pair [a, b]
in dislikes means a and b must not be in the same room. There are two rooms, of
any size. Return true if everyone can be placed, and false otherwise.
Examples
| Input | Output | Why |
|---|---|---|
n = 4, dislikes = [[1,2],[1,3],[2,4]] | true | Room one {1, 4}, room two {2, 3}. |
n = 3, dislikes = [[1,2],[1,3],[2,3]] | false | Three people who all dislike each other need three rooms. |
n = 6, dislikes = [[1,2],[3,4],[5,6],[2,3],[4,5],[6,1]] | true | A cycle of six alternates: {1, 3, 5} and {2, 4, 6}. |
Constraints
- 1 ≤ n ≤ 2000
- 0 ≤ dislikes.length ≤ 10⁴
- 1 ≤ a < b ≤ n; no pair repeats
Hints
Hint 1
Draw each dislike as an edge. The two rooms are two colours, and every edge must join different colours.
Hint 2
This is exactly the bipartite check: colour by breadth-first search from every uncoloured person.
Hint 3
Labels start at 1, so size your arrays n + 1.
Where people lose marks
- Off-by-one from the 1-based labels.
- Assigning rooms greedily in input order without propagating forced choices.
- Starting a single search from person 1 and ignoring people in other pieces.
Dry run on 4,[[1,2],[1,3],[2,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 / 4
1 is uncoloured: give it colour A and start a breadth-first search.
-
Step 2 / 4
From 1: its uncoloured neighbours 2, 3 get the opposite colour, B.
-
Step 3 / 4
From 2: its uncoloured neighbours 4 get the opposite colour, A.
-
Step 4 / 4
Every edge joins colour A to colour B: the graph is bipartite.
How to think about it
Build the graph whose edges are the dislikes. A valid placement gives every person one of two rooms with every edge between rooms: precisely a two-colouring. So the answer is "is this graph bipartite?", decided by colouring each connected piece with breadth-first search and failing on any edge whose ends receive the same colour.
An odd cycle of dislikes (three people who all dislike each other, or five in a ring) is exactly what makes it impossible.
Complexity. Time O(n + d) · Space O(n + d)
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 possible_bipartition(n, dislikes):
adj = [[] for _ in range(n + 1)] # people are 1..n
for a, b in dislikes:
adj[a].append(b)
adj[b].append(a)
room = [-1] * (n + 1)
for s in range(1, n + 1):
if room[s] != -1:
continue
room[s] = 0
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if room[v] == -1:
room[v] = 1 - room[u] # must be in the other room
q.append(v)
elif room[v] == room[u]:
return False
return True#include <queue>
#include <vector>
bool solve(int n, const std::vector<std::vector<int>>& dislikes) {
std::vector<std::vector<int>> adj(n + 1); // people are 1..n
for (const auto& d : dislikes) { adj[d[0]].push_back(d[1]); adj[d[1]].push_back(d[0]); }
std::vector<int> room(n + 1, -1);
for (int s = 1; s <= n; ++s) {
if (room[s] != -1) continue;
room[s] = 0;
std::queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : adj[u]) {
if (room[v] == -1) { room[v] = 1 - room[u]; q.push(v); } // the other room
else if (room[v] == room[u]) return false;
}
}
}
return true;
}import java.util.*;
class Solution {
public boolean possibleBipartition(int n, int[][] dislikes) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i <= n; i++) adj.add(new ArrayList<>()); // people are 1..n
for (int[] d : dislikes) { adj.get(d[0]).add(d[1]); adj.get(d[1]).add(d[0]); }
int[] room = new int[n + 1];
Arrays.fill(room, -1);
for (int s = 1; s <= n; s++) {
if (room[s] != -1) continue;
room[s] = 0;
ArrayDeque<Integer> q = new ArrayDeque<>();
q.add(s);
while (!q.isEmpty()) {
int u = q.poll();
for (int v : adj.get(u)) {
if (room[v] == -1) { room[v] = 1 - room[u]; q.add(v); } // the other room
else if (room[v] == room[u]) return false;
}
}
}
return true;
}
}function possibleBipartition(n, dislikes) {
const adj = Array.from({ length: n + 1 }, () => []); // people are 1..n
for (const [a, b] of dislikes) { adj[a].push(b); adj[b].push(a); }
const room = new Array(n + 1).fill(-1);
for (let s = 1; s <= n; s++) {
if (room[s] !== -1) continue;
room[s] = 0;
const q = [s];
for (let head = 0; head < q.length; head++) {
const u = q[head];
for (const v of adj[u]) {
if (room[v] === -1) { room[v] = 1 - room[u]; q.push(v); } // the other room
else if (room[v] === room[u]) return false;
}
}
}
return true;
}use std::collections::VecDeque;
fn solve(n: i64, dislikes: &[[i64; 2]]) -> bool {
let n = n as usize;
let mut adj = vec![Vec::new(); n + 1]; // people are 1..n
for d in dislikes {
let (a, b) = (d[0] as usize, d[1] as usize);
adj[a].push(b);
adj[b].push(a);
}
let mut room = vec![-1i8; n + 1];
for s in 1..=n {
if room[s] != -1 {
continue;
}
room[s] = 0;
let mut q = VecDeque::from([s]);
while let Some(u) = q.pop_front() {
for &v in &adj[u] {
if room[v] == -1 {
room[v] = 1 - room[u]; // the other room
q.push_back(v);
} else if room[v] == room[u] {
return false;
}
}
}
}
true
}import qualified Data.Map.Strict as M
import Data.Sequence (Seq (..), (|>))
import qualified Data.Sequence as Q
solve :: Int -> [[Int]] -> Bool
solve n dislikes = go [1 .. n] M.empty
where
adj = M.fromListWith (++) (concat [[(a, [b]), (b, [a])] | [a, b] <- dislikes])
go [] _ = True
go (s : rest) room
| M.member s room = go rest room
| otherwise = maybe False (go rest) (place (Q.singleton s) (M.insert s False room))
-- breadth-first: everyone a person dislikes goes in the other room
place Empty room = Just room
place (u :<| q) room = step (M.findWithDefault [] u adj) q room
where
ru = room M.! u
step [] q' r = place q' r
step (v : vs) q' r = case M.lookup v r of
Nothing -> step vs (q' |> v) (M.insert v (not ru) r)
Just rv | rv == ru -> Nothing
| otherwise -> step vs q' r
Variants to try
- There are three rooms: three-colouring is NP-hard in general; for n ≤ 15, try assignments with pruning.
- Rooms have a capacity of n/2 each: choose, for every piece, which side goes in which room (a subset-sum problem).
- Some pairs must share a room: merge them with union-find first, then check the dislikes.
Next problems
Can the graph be split in two?
Return true if the nodes can be coloured with two colours so that every edge joins different colours. Colour while you search.
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
Can the graph be split in two?
Return true if the nodes can be coloured with two colours so that every edge joins different colours. Colour while you search.
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.