Given an undirected graph, determine if it's bipartite. A graph is bipartite if the nodes can be colored in one of two colors, so that no two adjacent nodes are the same color.
The input is presented as an adjacency list, where graph[i] is a list of all nodes adjacent to node i.
Input: graph = [[1, 4], [0, 2], [1], [4], [0, 3]]
Output: True
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
You must seat guests at two tables so that no two people who dislike each other share a table. Sit the first person at table A; everyone they dislike must go to table B; everyone those people dislike comes back to table A; and so on. If you ever must seat someone at a table where an enemy already sits, it is impossible — the graph is not bipartite. That alternating A/B/A/B walk is exactly BFS 2-coloring.
Before diving into a solution, let’s first understand what makes a graph bipartite. If the nodes of a graph can be divided into two distinct sets, with the edges only running between nodes from different sets, then the graph is bipartite. We can visually rearrange the nodes of the following graph to demonstrate this:
With a graph that isn’t bipartite, it’s impossible to arrange the nodes this way without an edge existing between two nodes of the same set:
To determine if a graph is bipartite, we can use graph coloring, where we attempt to color one set of nodes with one color, and the other set of nodes with another color, while ensuring no adjacent nodes share the same color. Let’s explore this idea.
Graph coloring
Let's use blue and orange in our coloring process. One potential strategy is: for each node we color blue, color all of its neighbors orange, and vice versa. Most traversal algorithms allow us to color neighboring nodes in this way. In this explanation, we use DFS.
Let’s try applying this coloring process to the example below and see how it works:
Start by coloring node 0 blue:
Using DFS, we explore the neighbors of node 0, starting with node 1. All of its neighbors need to be colored orange, so let’s make a DFS call to node 1 to color it orange:
Let’s continue doing this for the next few nodes in the DFS process:
Above, we encountered an issue. We needed to color node 4 blue, but one of node 4’s neighbors, node 0, is also colored blue. This means the graph cannot be colored using our graph coloring strategy. In other words, the graph is not bipartite.
We now have a strategy to confirm if a graph is bipartite using the two-coloring technique. But how can we be sure that it always works? We’ve been coloring adjacent nodes in alternating colors from the beginning of the DFS. This means if we encounter a situation where two adjacent nodes are the same color, it means there’s no way to color the graph differently, since we’ve been following the rule of using different colors for neighboring nodes all along.
Handling multiple components
Keep in mind the input isn't necessarily a graph that's fully connected. It could be a graph with multiple components, such as this:
As such, we need to ensure we color all components of the graph by calling DFS on every uncolored node:
If we can confirm that all components can be colored using two colors, the graph is bipartite. However, if any of these components cannot be colored this way, the graph is not bipartite.
In this implementation, we color the nodes blue and orange using the numbers 1 and -1 to represent these colors, respectively. To keep track of each node's color, we use an array called colors, initialized with all 0s, where 0 represents an unvisited node. As we explore the graph using DFS, we update the colors array by setting each node to either 1 (blue) or -1 (orange).
def bipartite_graph_validation(graph: List[List[int]]) -> bool:
colors = [0] * len(graph)
# Determine if each graph component is bipartite.
for i in range(len(graph)):
if colors[i] == 0 and not dfs(i, 1, graph, colors):
return False
return True
def dfs(node: int, color: int, graph: List[List[int]], colors: List[int]) -> bool:
colors[node] = color
for neighbor in graph[node]:
# If the current neighbor has the same color as the current node, the graph is
# not bipartite.
if colors[neighbor] == color:
return False
# If the current neighbor is not colored, color it with the other color and
# continue the DFS.
if colors[neighbor] == 0 and not dfs(neighbor, -color, graph, colors):
return False
return True#include <vector>
#include <queue>
using namespace std;
bool bipartiteGraphValidation(vector<vector<int>>& graph) {
int n = graph.size();
vector<int> colors(n, 0); // 0 = uncolored; 1 and -1 are the two colors
for (int start = 0; start < n; start++) {
if (colors[start] != 0) {
continue; // already handled by an earlier BFS
}
colors[start] = 1;
queue<int> q;
q.push(start);
while (!q.empty()) {
int node = q.front();
q.pop();
for (int neighbor : graph[node]) {
if (colors[neighbor] == 0) {
colors[neighbor] = -colors[node]; // opposite color
q.push(neighbor);
} else if (colors[neighbor] == colors[node]) {
return false; // edge with two equal colors
}
}
}
}
return true;
}boolean bipartiteGraphValidation(List<List<Integer>> graph) {
int n = graph.size();
int[] colors = new int[n]; // 0 = uncolored; 1 and -1 are the two colors
for (int start = 0; start < n; start++) {
if (colors[start] != 0) {
continue; // already handled by an earlier BFS
}
colors[start] = 1;
Queue<Integer> q = new LinkedList<>();
q.add(start);
while (!q.isEmpty()) {
int node = q.poll();
for (int neighbor : graph.get(node)) {
if (colors[neighbor] == 0) {
colors[neighbor] = -colors[node]; // opposite color
q.add(neighbor);
} else if (colors[neighbor] == colors[node]) {
return false; // edge with two equal colors
}
}
}
}
return true;
}Time complexity: The time complexity of bipartite_graph_validation is where denotes the number of nodes and denotes the number of edges. This is because we explore all nodes in the graph and traverse across edges during DFS.
Space complexity: The space complexity is due to the space taken up by the recursive call stack, which can grow as large as . In addition, the colors array also contributes space.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
graph = [[1,3],[0,2],[1,3],[0,2]]. In the diagrams, green = color A, blue = color B, gray = uncolored. Start at node 0 colored A.
| pop node | its color | action | colors so far |
|---|---|---|---|
| 0 | A | paint 1→B, 3→B; enqueue both | 0:A 1:B 3:B |
| 1 | B | neighbor 0 is A (ok); paint 2→A; enqueue 2 | +2:A |
| 3 | B | neighbor 0 is A (ok); neighbor 2 is A (ok) | unchanged |
| 2 | A | neighbors 1,3 both B (ok); queue empties | all colored, no clash |
Every edge joins an A to a B → True. Even cycles are always bipartite. ✓
graph = [[1,2],[0,2],[0,1]]. Three nodes, all connected to each other (an odd cycle).
| pop node | its color | action | result |
|---|---|---|---|
| 0 | A | paint 1→B and 2→B; enqueue both | 0:A 1:B 2:B |
| 1 | B | neighbor 2 is already B == color of 1 → clash | return False |
Any odd-length cycle breaks two-coloring — that is exactly what “not bipartite” means. ✗
Colors live in a list of 0/1/-1. The opposite color is -colors[node]. The outer for start in range(n) covers every component.
Same steps with a vector<int> of colors and a queue<int>. -colors[node] flips 1 and -1. The membership test is just an integer compare.
Same steps with an int[] colors and a LinkedList queue. Default array values are 0, which conveniently means “uncolored.”