Before attempting this problem, you should be comfortable with:
Graph Representation - Understanding adjacency lists and how graphs are stored in memory
Depth-First Search (DFS) - Traversing graphs recursively or with an explicit stack
Breadth-First Search (BFS) - Level-order traversal of graphs using a queue
Union-Find (Disjoint Set Union) - Efficiently grouping and querying connected components
1. Depth First Search
Intuition
A graph is bipartite if we can split its nodes into two groups such that every edge connects nodes from different groups. This is equivalent to checking if the graph is 2-colorable. Using DFS, we assign a color to the starting node and then assign the opposite color to all its neighbors. If we ever find a neighbor that already has the same color as the current node, the graph is not bipartite. Since the graph may be disconnected, we run dfs from every unvisited node.
Algorithm
Create a color array initialized to 0 (unvisited). Use 1 and -1 as the two colors.
Define a recursive dfs function that takes a node i and a color c:
Assign color[i] = c.
For each neighbor, if it has the same color, return false.
If the neighbor is unvisited, recursively call dfs with the opposite color. If that returns false, propagate the failure.
Return true if all neighbors pass.
For each node, if unvisited, run dfs with color 1. If any dfs fails, return false.
classSolution:defisBipartite(self, graph: List[List[int]])->bool:
color =[0]*len(graph)# Map node i -> odd=1, even=-1defdfs(i, c):
color[i]= c
for nei in graph[i]:if color[nei]== c:returnFalseif color[nei]==0andnot dfs(nei,-c):returnFalsereturnTruefor i inrange(len(graph)):if color[i]==0andnot dfs(i,1):returnFalsereturnTrue
publicclassSolution{privateint[] color;publicbooleanisBipartite(int[][] graph){int n = graph.length;
color =newint[n];// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]==0&&!dfs(graph, i,1)){returnfalse;}}returntrue;}privatebooleandfs(int[][] graph,int i,int c){
color[i]= c;for(int nei : graph[i]){if(color[nei]== c){returnfalse;}if(color[nei]==0&&!dfs(graph, nei,-c)){returnfalse;}}returntrue;}}
classSolution{private:
vector<int> color;booldfs(vector<vector<int>>& graph,int i,int c){
color[i]= c;for(int nei : graph[i]){if(color[nei]== c){returnfalse;}if(color[nei]==0&&!dfs(graph, nei,-c)){returnfalse;}}returntrue;}public:boolisBipartite(vector<vector<int>>& graph){int n = graph.size();
color.assign(n,0);// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]==0&&!dfs(graph, i,1)){returnfalse;}}returntrue;}};
classSolution{/**
* @param {number[][]} graph
* @return {boolean}
*/isBipartite(graph){let n = graph.length;let color =newArray(n).fill(0);// Map node i -> odd=1, even=-1constdfs=(i, c)=>{
color[i]= c;for(let nei of graph[i]){if(color[nei]=== c){returnfalse;}if(color[nei]===0&&!dfs(nei,-c)){returnfalse;}}returntrue;};for(let i =0; i < n; i++){if(color[i]===0&&!dfs(i,1)){returnfalse;}}returntrue;}}
publicclassSolution{privateint[] color;publicboolIsBipartite(int[][] graph){int n = graph.Length;
color =newint[n];// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]==0&&!Dfs(graph, i,1)){returnfalse;}}returntrue;}privateboolDfs(int[][] graph,int i,int c){
color[i]= c;foreach(int nei in graph[i]){if(color[nei]== c){returnfalse;}if(color[nei]==0&&!Dfs(graph, nei,-c)){returnfalse;}}returntrue;}}
funcisBipartite(graph [][]int)bool{
n :=len(graph)
color :=make([]int, n)// Map node i -> odd=1, even=-1var dfs func(i, c int)bool
dfs =func(i, c int)bool{
color[i]= c
for_, nei :=range graph[i]{if color[nei]== c {returnfalse}if color[nei]==0&&!dfs(nei,-c){returnfalse}}returntrue}for i :=0; i < n; i++{if color[i]==0&&!dfs(i,1){returnfalse}}returntrue}
class Solution {privatelateinitvar color: IntArray
funisBipartite(graph: Array<IntArray>): Boolean {val n = graph.size
color =IntArray(n)// Map node i -> odd=1, even=-1for(i in0 until n){if(color[i]==0&&!dfs(graph, i,1)){returnfalse}}returntrue}privatefundfs(graph: Array<IntArray>, i: Int, c: Int): Boolean {
color[i]= c
for(nei in graph[i]){if(color[nei]== c){returnfalse}if(color[nei]==0&&!dfs(graph, nei,-c)){returnfalse}}returntrue}}
classSolution{privatevar color =[Int]()funcisBipartite(_ graph:[[Int]])->Bool{let n = graph.count
color =[Int](repeating:0, count: n)// Map node i -> odd=1, even=-1for i in0..<n {if color[i]==0&&!dfs(graph, i,1){returnfalse}}returntrue}privatefuncdfs(_ graph:[[Int]],_ i:Int,_ c:Int)->Bool{
color[i]= c
for nei in graph[i]{if color[nei]== c {returnfalse}if color[nei]==0&&!dfs(graph, nei,-c){returnfalse}}returntrue}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V)
Where V is the number of vertices and E is the number of edges.
2. Breadth First Search
Intuition
BFS provides another way to check 2-colorability. Starting from an uncolored node, we assign it a color and add it to a queue. For each node we process, we check all neighbors: if a neighbor has the same color, the graph is not bipartite; if uncolored, we assign it the opposite color and enqueue it. BFS naturally explores the graph level by level, alternating colors between levels.
Algorithm
Create a color array initialized to 0.
For each node i from 0 to n-1:
If already colored, skip it.
Initialize a queue with node i and set color[i] = -1.
While the queue is not empty:
Dequeue a node.
For each neighbor: if it has the same color, return false. If uncolored, assign the opposite color and enqueue it.
Return true if all nodes are processed without conflict.
classSolution:defisBipartite(self, graph: List[List[int]])->bool:
color =[0]*len(graph)# Map node i -> odd=1, even=-1defbfs(i):if color[i]:returnTrue
q = deque([i])
color[i]=-1while q:
i = q.popleft()for nei in graph[i]:if color[i]== color[nei]:returnFalseelifnot color[nei]:
q.append(nei)
color[nei]=-1* color[i]returnTruefor i inrange(len(graph)):ifnot bfs(i):returnFalsereturnTrue
publicclassSolution{publicbooleanisBipartite(int[][] graph){int n = graph.length;int[] color =newint[n];// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]!=0)continue;Queue<Integer> q =newLinkedList<>();
q.offer(i);
color[i]=-1;while(!q.isEmpty()){int node = q.poll();for(int nei : graph[node]){if(color[nei]== color[node]){returnfalse;}elseif(color[nei]==0){
q.offer(nei);
color[nei]=-color[node];}}}}returntrue;}}
classSolution{public:boolisBipartite(vector<vector<int>>& graph){int n = graph.size();
vector<int>color(n,0);// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]!=0)continue;
queue<int> q;
q.push(i);
color[i]=-1;while(!q.empty()){int node = q.front();
q.pop();for(int nei : graph[node]){if(color[nei]== color[node]){returnfalse;}elseif(color[nei]==0){
q.push(nei);
color[nei]=-color[node];}}}}returntrue;}};
classSolution{/**
* @param {number[][]} graph
* @return {boolean}
*/isBipartite(graph){let n = graph.length;let color =newArray(n).fill(0);// Map node i -> odd=1, even=-1for(let i =0; i < n; i++){if(color[i]!==0)continue;let q =newQueue([i]);
color[i]=-1;while(!q.isEmpty()){let node = q.pop();for(let nei of graph[node]){if(color[nei]=== color[node]){returnfalse;}elseif(color[nei]===0){
q.push(nei);
color[nei]=-color[node];}}}}returntrue;}}
publicclassSolution{publicboolIsBipartite(int[][] graph){int n = graph.Length;int[] color =newint[n];// Map node i -> odd=1, even=-1for(int i =0; i < n; i++){if(color[i]!=0)continue;Queue<int> q =newQueue<int>();
q.Enqueue(i);
color[i]=-1;while(q.Count >0){int node = q.Dequeue();foreach(int nei in graph[node]){if(color[nei]== color[node]){returnfalse;}elseif(color[nei]==0){
q.Enqueue(nei);
color[nei]=-color[node];}}}}returntrue;}}
funcisBipartite(graph [][]int)bool{
n :=len(graph)
color :=make([]int, n)// Map node i -> odd=1, even=-1for i :=0; i < n; i++{if color[i]!=0{continue}
q :=[]int{i}
color[i]=-1forlen(q)>0{
node := q[0]
q = q[1:]for_, nei :=range graph[node]{if color[nei]== color[node]{returnfalse}elseif color[nei]==0{
q =append(q, nei)
color[nei]=-color[node]}}}}returntrue}
class Solution {funisBipartite(graph: Array<IntArray>): Boolean {val n = graph.size
val color =IntArray(n)// Map node i -> odd=1, even=-1for(i in0 until n){if(color[i]!=0)continueval q: Queue<Int>=LinkedList()
q.offer(i)
color[i]=-1while(q.isNotEmpty()){val node = q.poll()for(nei in graph[node]){if(color[nei]== color[node]){returnfalse}elseif(color[nei]==0){
q.offer(nei)
color[nei]=-color[node]}}}}returntrue}}
classSolution{funcisBipartite(_ graph:[[Int]])->Bool{let n = graph.count
var color =[Int](repeating:0, count: n)// Map node i -> odd=1, even=-1for i in0..<n {if color[i]!=0{continue}var q =[i]
color[i]=-1while!q.isEmpty {let node = q.removeFirst()for nei in graph[node]{if color[nei]== color[node]{returnfalse}elseif color[nei]==0{
q.append(nei)
color[nei]=-color[node]}}}}returntrue}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V)
Where V is the number of vertices and E is the number of edges.
3. Iterative DFS
Intuition
Iterative DFS uses an explicit stack instead of recursion to traverse the graph. The coloring logic remains the same: assign a color to a node, then process all neighbors by checking for conflicts and pushing uncolored neighbors onto the stack with the opposite color. This avoids potential stack overflow issues with very deep graphs while achieving the same result as recursive DFS.
Algorithm
Create a color array initialized to 0.
For each node i from 0 to n-1:
If already colored, skip it.
Set color[i] = -1 and push i onto the stack.
While the stack is not empty:
Pop a node.
For each neighbor: if it has the same color, return false. If uncolored, assign the opposite color and push it onto the stack.
classSolution:defisBipartite(self, graph: List[List[int]])->bool:
color =[0]*len(graph)# Map node i -> odd=1, even=-1
stack =[]for i inrange(len(graph)):if color[i]!=0:continue
color[i]=-1
stack.append(i)while stack:
node = stack.pop()for nei in graph[node]:if color[node]== color[nei]:returnFalseelifnot color[nei]:
stack.append(nei)
color[nei]=-1* color[node]returnTrue
publicclassSolution{publicbooleanisBipartite(int[][] graph){int n = graph.length;int[] color =newint[n];// Map node i -> odd=1, even=-1Stack<Integer> stack =newStack<>();for(int i =0; i < n; i++){if(color[i]!=0)continue;
color[i]=-1;
stack.push(i);while(!stack.isEmpty()){int node = stack.pop();for(int nei : graph[node]){if(color[node]== color[nei])returnfalse;if(color[nei]==0){
stack.push(nei);
color[nei]=-color[node];}}}}returntrue;}}
classSolution{public:boolisBipartite(vector<vector<int>>& graph){int n = graph.size();
vector<int>color(n);// Map node i -> odd=1, even=-1
stack<int> stack;for(int i =0; i < n; i++){if(color[i]!=0)continue;
color[i]=-1;
stack.push(i);while(!stack.empty()){int node = stack.top();
stack.pop();for(int nei : graph[node]){if(color[node]== color[nei])returnfalse;if(color[nei]==0){
stack.push(nei);
color[nei]=-color[node];}}}}returntrue;}};
classSolution{/**
* @param {number[][]} graph
* @return {boolean}
*/isBipartite(graph){let n = graph.length;let color =newArray(n).fill(0);// Map node i -> odd=1, even=-1let stack =[];for(let i =0; i < n; i++){if(color[i]!==0)continue;
color[i]=-1;
stack.push(i);while(stack.length >0){let node = stack.pop();for(let nei of graph[node]){if(color[node]=== color[nei])returnfalse;if(color[nei]===0){
stack.push(nei);
color[nei]=-color[node];}}}}returntrue;}}
publicclassSolution{publicboolIsBipartite(int[][] graph){int n = graph.Length;int[] color =newint[n];// Map node i -> odd=1, even=-1Stack<int> stack =newStack<int>();for(int i =0; i < n; i++){if(color[i]!=0)continue;
color[i]=-1;
stack.Push(i);while(stack.Count >0){int node = stack.Pop();foreach(int nei in graph[node]){if(color[node]== color[nei])returnfalse;if(color[nei]==0){
stack.Push(nei);
color[nei]=-color[node];}}}}returntrue;}}
funcisBipartite(graph [][]int)bool{
n :=len(graph)
color :=make([]int, n)// Map node i -> odd=1, even=-1
stack :=[]int{}for i :=0; i < n; i++{if color[i]!=0{continue}
color[i]=-1
stack =append(stack, i)forlen(stack)>0{
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]for_, nei :=range graph[node]{if color[node]== color[nei]{returnfalse}if color[nei]==0{
stack =append(stack, nei)
color[nei]=-color[node]}}}}returntrue}
class Solution {funisBipartite(graph: Array<IntArray>): Boolean {val n = graph.size
val color =IntArray(n)// Map node i -> odd=1, even=-1val stack = ArrayDeque<Int>()for(i in0 until n){if(color[i]!=0)continue
color[i]=-1
stack.addLast(i)while(stack.isNotEmpty()){val node = stack.removeLast()for(nei in graph[node]){if(color[node]== color[nei])returnfalseif(color[nei]==0){
stack.addLast(nei)
color[nei]=-color[node]}}}}returntrue}}
classSolution{funcisBipartite(_ graph:[[Int]])->Bool{let n = graph.count
var color =[Int](repeating:0, count: n)// Map node i -> odd=1, even=-1var stack =[Int]()for i in0..<n {if color[i]!=0{continue}
color[i]=-1
stack.append(i)while!stack.isEmpty {let node = stack.removeLast()for nei in graph[node]{if color[node]== color[nei]{returnfalse}if color[nei]==0{
stack.append(nei)
color[nei]=-color[node]}}}}returntrue}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V)
Where V is the number of vertices and E is the number of edges.
4. Disjoint Set Union
Intuition
DSU (Union-Find) offers an alternative perspective. For a bipartite graph, a node must be in a different set than all of its neighbors, but all neighbors should belong to the same set. For each node, we union all its neighbors together. If a node ever ends up in the same set as one of its neighbors, the graph is not bipartite. This works because being in the same connected component in the neighbor graph implies they must share a color in a valid 2-coloring.
Algorithm
Initialize a DSU structure with n nodes.
For each node:
If it has no neighbors, continue.
For each neighbor:
If the node and neighbor are in the same set, return false.
Union the neighbor with the first neighbor of the current node (grouping all neighbors together).
publicclassDSU{privateint[] Parent, Size;publicDSU(int n){
Parent =newint[n];
Size =newint[n];for(int i =0; i < n; i++){
Parent[i]= i;
Size[i]=0;}}publicintFind(int node){if(Parent[node]!= node){
Parent[node]=Find(Parent[node]);}return Parent[node];}publicboolUnion(int u,int v){int pu =Find(u), pv =Find(v);if(pu == pv)returnfalse;if(Size[pu]> Size[pv]){
Parent[pv]= pu;}elseif(Size[pu]< Size[pv]){
Parent[pu]= pv;}else{
Parent[pv]= pu;
Size[pu]++;}returntrue;}}publicclassSolution{publicboolIsBipartite(int[][] graph){int n = graph.Length;DSU dsu =newDSU(n);for(int node =0; node < n; node++){foreach(int nei in graph[node]){if(dsu.Find(node)== dsu.Find(nei)){returnfalse;}
dsu.Union(graph[node][0], nei);}}returntrue;}}
type DSU struct{
Parent []int
Size []int}funcNewDSU(n int)*DSU {
parent :=make([]int, n)
size :=make([]int, n)for i :=0; i < n; i++{
parent[i]= i
}return&DSU{Parent: parent, Size: size}}func(d *DSU)Find(node int)int{if d.Parent[node]!= node {
d.Parent[node]= d.Find(d.Parent[node])}return d.Parent[node]}func(d *DSU)Union(u, v int)bool{
pu, pv := d.Find(u), d.Find(v)if pu == pv {returnfalse}if d.Size[pu]> d.Size[pv]{
d.Parent[pv]= pu
}elseif d.Size[pu]< d.Size[pv]{
d.Parent[pu]= pv
}else{
d.Parent[pv]= pu
d.Size[pu]++}returntrue}funcisBipartite(graph [][]int)bool{
n :=len(graph)
dsu :=NewDSU(n)for node :=0; node < n; node++{for_, nei :=range graph[node]{if dsu.Find(node)== dsu.Find(nei){returnfalse}
dsu.Union(graph[node][0], nei)}}returntrue}
classDSU(n: Int){privateval parent =IntArray(n){ it }privateval size =IntArray(n)funfind(node: Int): Int {if(parent[node]!= node){
parent[node]=find(parent[node])}return parent[node]}fununion(u: Int, v: Int): Boolean {val pu =find(u)val pv =find(v)if(pu == pv)returnfalseif(size[pu]> size[pv]){
parent[pv]= pu
}elseif(size[pu]< size[pv]){
parent[pu]= pv
}else{
parent[pv]= pu
size[pu]++}returntrue}}class Solution {funisBipartite(graph: Array<IntArray>): Boolean {val n = graph.size
val dsu =DSU(n)for(node in0 until n){for(nei in graph[node]){if(dsu.find(node)== dsu.find(nei)){returnfalse}
dsu.union(graph[node][0], nei)}}returntrue}}
classDSU{privatevar parent:[Int]privatevar size:[Int]init(_ n:Int){
parent =Array(0..<n)
size =[Int](repeating:0, count: n)}funcfind(_ node:Int)->Int{if parent[node]!= node {
parent[node]=find(parent[node])}return parent[node]}funcunion(_ u:Int,_ v:Int)->Bool{let pu =find(u), pv =find(v)if pu == pv {returnfalse}if size[pu]> size[pv]{
parent[pv]= pu
}elseif size[pu]< size[pv]{
parent[pu]= pv
}else{
parent[pv]= pu
size[pu]+=1}returntrue}}classSolution{funcisBipartite(_ graph:[[Int]])->Bool{let n = graph.count
let dsu =DSU(n)for node in0..<n {for nei in graph[node]{if dsu.find(node)== dsu.find(nei){returnfalse}
dsu.union(graph[node][0], nei)}}returntrue}}
Time & Space Complexity
Time complexity: O(V+(E∗α(V)))
Space complexity: O(V)
Where V is the number of vertices and E is the number of edges. α() is used for amortized complexity.
Common Pitfalls
Forgetting to Handle Disconnected Components
The graph may consist of multiple disconnected components. If you only run BFS/DFS from node 0, you will miss other components that might not be bipartite. Always iterate through all nodes and start a new traversal from any unvisited node to ensure every component is checked.
Checking Only Unvisited Neighbors
When traversing the graph, you must check the color of all neighbors, not just unvisited ones. If a neighbor is already colored with the same color as the current node, the graph is not bipartite. Skipping already-visited neighbors misses these conflict cases.
Using Wrong Initial Color Values
Using 0 as a valid color while also using it to represent "unvisited" creates ambiguity. A clean approach is to use 0 for unvisited nodes and 1/-1 as the two actual colors. Alternatively, use a separate visited array. Mixing up these states leads to incorrect bipartiteness detection.