A clear explanation of checking whether people can be split into two groups using graph coloring and bipartite graph detection.
Problem Restatement
We are given n people labeled from 1 to n.
We are also given a list dislikes, where each pair:
[a, b]means person a dislikes person b, and person b dislikes person a.
We need to split everyone into two groups so that no pair of people who dislike each other are in the same group.
Return true if this is possible. Otherwise, return false.
This is exactly a bipartite graph problem: people are nodes, dislikes are edges, and we need to color the graph with two colors so that every edge connects nodes of different colors. The official constraints allow n <= 2000 and up to 10^4 dislike pairs.
Input and Output
| Item | Meaning |
|---|---|
| Input | n, the number of people |
| Input | dislikes, a list of dislike pairs |
| Output | true if a valid split exists, otherwise false |
| Person labels | 1 through n |
| Relationship | Undirected |
| Group count | Exactly two groups are allowed |
Function shape:
def possibleBipartition(self, n: int, dislikes: list[list[int]]) -> bool:
...Examples
Example 1:
Input: n = 4, dislikes = [[1,2],[1,3],[2,4]]
Output: trueOne valid split is:
| Group | People |
|---|---|
| Group 1 | 1, 4 |
| Group 2 | 2, 3 |
Check every dislike pair:
| Pair | Same group? |
|---|---|
[1,2] | No |
[1,3] | No |
[2,4] | No |
So the answer is true.
Example 2:
Input: n = 3, dislikes = [[1,2],[1,3],[2,3]]
Output: falseThis forms a triangle:
1 dislikes 2
1 dislikes 3
2 dislikes 3With only two groups, two of these people must share a group. But every pair dislikes each other, so this is impossible.
First Thought: Try Assigning Groups Recursively
A direct idea is to try all possible assignments of people to two groups.
Each person can go into group A or group B.
For n people, that gives:
2^npossible assignments.
For each assignment, we could check whether any dislike pair is inside the same group.
This is correct, but much too slow for n <= 2000.
We need to use the structure of the dislike relationships.
Key Insight
Think of the problem as a graph.
| Problem object | Graph object |
|---|---|
| Person | Node |
| Dislike pair | Undirected edge |
| Group | Color |
We need to color every node using two colors.
For every edge [a, b], nodes a and b must have different colors.
So the question becomes:
Is the graph bipartite?A graph is bipartite if its nodes can be divided into two sets so that every edge goes between the two sets.
We can test this using DFS or BFS.
Algorithm
Build an adjacency list.
For each dislike pair [a, b]:
graph[a].append(b)
graph[b].append(a)Use an array color.
| Value | Meaning |
|---|---|
0 | Uncolored |
1 | First group |
-1 | Second group |
Then scan all people from 1 to n.
If a person is uncolored, start a DFS or BFS from that person and assign color 1.
For each edge from person to neighbor:
- If
neighboris uncolored, color it with the opposite color. - If
neighboralready has the same color asperson, returnfalse.
If all connected components are colored successfully, return true.
We must scan all people because the graph may be disconnected.
Walkthrough
Use:
n = 4
dislikes = [[1,2],[1,3],[2,4]]Build the graph:
| Person | Dislikes |
|---|---|
1 | 2, 3 |
2 | 1, 4 |
3 | 1 |
4 | 2 |
Start at person 1.
Assign:
color[1] = 1Neighbors 2 and 3 must get the opposite color:
color[2] = -1
color[3] = -1From person 2, neighbor 4 must get the opposite color:
color[4] = 1Final colors:
| Person | Color |
|---|---|
1 | 1 |
2 | -1 |
3 | -1 |
4 | 1 |
Every dislike edge connects different colors, so the split is possible.
Correctness
The algorithm assigns colors so that every dislike edge connects two people in different groups.
When the algorithm visits an edge from person to neighbor, there are two cases.
If neighbor has no color yet, the algorithm gives it the opposite color from person. This satisfies the edge constraint for that edge.
If neighbor already has a color, the algorithm checks whether it equals color[person]. If the colors are equal, then the two people would be in the same group despite disliking each other. No valid bipartition can keep this coloring and satisfy the edge, so the algorithm returns false.
If no conflict is found, every checked dislike edge connects different colors.
The algorithm starts a traversal from every uncolored person, so it covers all connected components, not only the component containing person 1.
Therefore, if the algorithm returns true, every dislike pair has endpoints in different groups, so a valid bipartition exists.
If a valid bipartition exists, graph coloring with two colors is possible. The traversal only forces neighbors to have opposite colors, which is required by every valid bipartition. Since no odd-cycle conflict exists in a bipartite graph, the algorithm will never find two adjacent nodes with the same color. Therefore, it will return true.
Thus, the algorithm is correct.
Complexity
Let:
V = n
E = len(dislikes)| Metric | Value | Why |
|---|---|---|
| Time | O(V + E) | We visit each person and each dislike edge |
| Space | O(V + E) | The graph stores all edges and the color array stores all people |
Implementation
from collections import deque
class Solution:
def possibleBipartition(self, n: int, dislikes: list[list[int]]) -> bool:
graph = [[] for _ in range(n + 1)]
for a, b in dislikes:
graph[a].append(b)
graph[b].append(a)
color = [0] * (n + 1)
for start in range(1, n + 1):
if color[start] != 0:
continue
color[start] = 1
queue = deque([start])
while queue:
person = queue.popleft()
for neighbor in graph[person]:
if color[neighbor] == 0:
color[neighbor] = -color[person]
queue.append(neighbor)
elif color[neighbor] == color[person]:
return False
return TrueCode Explanation
We create an adjacency list with n + 1 slots:
graph = [[] for _ in range(n + 1)]The people are labeled from 1 to n, so index 0 is unused.
For each dislike pair, we add both directions:
graph[a].append(b)
graph[b].append(a)This is an undirected graph.
The color array starts with all people uncolored:
color = [0] * (n + 1)We scan all people:
for start in range(1, n + 1):If a person already has a color, that person belongs to a component we have already processed.
If not, we start a BFS:
color[start] = 1
queue = deque([start])For every neighbor, if it has no color, we assign the opposite color:
color[neighbor] = -color[person]If a neighbor already has the same color, the bipartition is impossible:
elif color[neighbor] == color[person]:
return FalseIf the BFS finishes for every component without conflict, we return:
return TrueTesting
def run_tests():
s = Solution()
assert s.possibleBipartition(
4,
[[1, 2], [1, 3], [2, 4]]
) is True
assert s.possibleBipartition(
3,
[[1, 2], [1, 3], [2, 3]]
) is False
assert s.possibleBipartition(
5,
[[1, 2], [2, 3], [3, 4], [4, 5], [1, 5]]
) is False
assert s.possibleBipartition(
4,
[]
) is True
assert s.possibleBipartition(
6,
[[1, 2], [3, 4], [5, 6]]
) is True
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[[1,2],[1,3],[2,4]] | Standard valid split |
| Triangle conflict | Odd cycle cannot be bipartitioned |
| Five-node odd cycle | Larger conflict case |
| No dislikes | Everyone can be split arbitrarily |
| Disconnected components | Confirms all components are checked |