Is Graph Bipartite? medium
Description
There is an undirected graph with n nodes numbered 0..n-1, given as an array graph where graph[u] is a list of uβs neighbors. Return true if the graph is bipartite.
A graph is bipartite if you can split its vertices into two disjoint sets A and B such that every edge connects a vertex in A to a vertex in B β never within the same set.
Examples
> Case 1:
graph = [[1,2,3], [0,2], [0,1,3], [0,2]]
Output: false # node 1 connects to node 2 β but both would need different colors
> Case 2:
graph = [[1,3], [0,2], [1,3], [0,2]]
Output: true # Split: {0, 2} and {1, 3}.Constraints
1 <= graph.length <= 1000 <= graph[u].length < graph.length- The graph may be disconnected.
The 2-coloring idea
The defining property: bipartite β 2-colorable β no odd-length cycles.
So the check becomes: try to color the graph with two colors such that no edge connects two same-colored nodes. If you ever fail, the graph is not bipartite.
The algorithm is BFS (or DFS) with one extra piece of state β the color:
- Start a node with color
0. - Color every neighbor with the opposite color of the current node.
- If a neighbor is already colored and disagrees with what weβd assign, the graph is not bipartite.
Why does that work?
A bipartite graph has no odd cycles. As BFS expands, alternating colors layer by layer, an odd cycle would force two nodes in the cycle to receive the same color β contradicting bipartiteness.
Code
The outer for start in range(n) loop is necessary because the graph might be disconnected. Each connected component needs its own BFS β but the coloring is independent across components, so we can start each one fresh with color 0.
DFS variant
Equivalent β just replace the queue with recursion:
def is_bipartite_dfs(graph):
n = len(graph)
color = [-1] * n
def dfs(u, c):
color[u] = c
for v in graph[u]:
if color[v] == -1:
if not dfs(v, 1 - c): return False
elif color[v] == c:
return False
return True
for start in range(n):
if color[start] == -1 and not dfs(start, 0):
return False
return TrueBoth versions are O(V + E). DFS is slightly shorter; BFS uses less stack depth on long thin graphs.
Trace on the bipartite example
graph = [[1,3], [0,2], [1,3], [0,2]]
Start at 0, color[0] = 0. Queue = [0].
Pop 0. Neighbors {1, 3} uncolored β color[1] = 1, color[3] = 1. Queue = [1, 3].
Pop 1. Neighbor 0 already color 0 (OK, different from 1). Neighbor 2 uncolored β color[2] = 0. Queue = [3, 2].
Pop 3. Neighbor 0 already 0 (OK), neighbor 2 already 0 (OK, different from 3's color 1).
Pop 2. Neighbor 1 already 1 (OK), neighbor 3 already 1 (OK).
All consistent β return true. Split: {0, 2} (color 0) and {1, 3} (color 1).Where bipartite checks show up in the real world
- Matching jobs to workers β workers and jobs are vertices, edges represent βworker can do job.β A bipartite graph means the matching is clean.
- Compiler register allocation β two variables that conflict need different registers; the conflict graph being bipartite means 2 colors (registers) suffice.
- Stable-marriage / dating apps β the underlying preference structure is bipartite.
- Scheduling exams to time slots β exams and slots are the two sides.
If a problem has two distinct kinds of things and edges only between kinds, youβve got a bipartite graph.
Analysis
- Time: O(V + E) β standard BFS/DFS bound.
- Space: O(V) for the color array + queue.