LeetCode #886 Medium

Possible Bipartition

Possible Bipartition is LeetCode 886 (Medium). There are n people, numbered 1 … n, to be split into two groups of any size. You are given dislikes, where dislikes[i] = [a, b] means person a and person b must not be in the same group.

Return true if it is possible to split everyone into two groups so that no two people who dislike each other share a group, and false otherwise.

  • A group may be empty, and people with no dislikes can go in either group.
  • Each pair appears at most once.

With up to 2,000 people and 10⁴ dislikes, one linear pass over the graph is enough.

Constraints
  • 1 <= n <= 2000
  • 0 <= dislikes.length <= 10⁴
  • dislikes[i].length == 2
  • 1 <= ai < bi <= n
  • All the pairs of dislikes are unique.
graphbfsdfscoloring
Open on LeetCode ↗
02

Intuition

Turn the people into a graph:

  • each person is a vertex;
  • each dislike pair is an edge between two vertices.

Splitting into two groups is then two-colouring: give every vertex one of two colours so that no edge joins two vertices of the same colour. A graph that can be coloured this way is a bipartite graph, so possible bipartition asks one question: is the dislike graph bipartite?

Colouring is forced once one vertex is chosen. Put any person in group A; everyone they dislike must go to group B; everyone those people dislike must go to A; and so on. BFS spreads these forced choices outward.

It fails only when an edge joins two vertices that were forced into the same group. That happens exactly when the graph has a cycle of odd length: going round an odd cycle, the colours alternate A, B, A, … and the last vertex ends up the same colour as its neighbour the first.

How to spot this pattern

Split into two sides so that certain pairs are always on opposite sides: bipartite check. The same code solves Is Graph Bipartite? (LeetCode 785), team splitting, and two-colouring a map. With more than two groups the problem becomes general graph colouring, which has no efficient exact algorithm.

03

Approach

Try it first

Before reading on: draw the dislike graph for the second example. Colour person 1 blue and follow the rules. Where does it go wrong? Then think about people who dislike nobody, and about groups of people with no link to person 1.

1

Build the dislike graph

An adjacency list over people 1 … n. Each pair [a, b] adds b to adj[a] and a to adj[b]. A dislike goes both ways, so both people need the edge.

2

Start a BFS from every uncoloured person

Give them colour 1 (group A) and spread from there. Every person still uncoloured after one BFS starts the next. The dislike graph can have several separate groups, and each needs its own starting colour. Starting only from person 1 would leave unconnected people unchecked.

3

Spread the forced colours

Pop u. For each neighbour v:

  • uncoloured: give it -color[u] (the other group) and queue it;
  • already coloured differently: nothing to do.

In a two-group split, a neighbour's group is forced to be the opposite one.

4

Stop on a clash

If a neighbour already has the same colour as u, two people who dislike each other are forced into one group: return false. A clash means some cycle has odd length, and no two-group split exists.

5

Return true

If every component is coloured without a clash, the two colours are the two groups. Every dislike edge now joins two different colours.

04

Possible Bipartition solution in Python | C++ | Java

▶1class Solution:
▶2 def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
▶3 adj = [[] for _ in range(n + 1)]
▶4 for a, b in dislikes:
▶5 adj[a].append(b)
▶6 adj[b].append(a)
▶7 # 0 = no group yet, 1 and -1 are the two groups
▶8 color = [0] * (n + 1)
▶9 for start in range(1, n + 1):
▶10 if color[start]:
▶11 continue
▶12 color[start] = 1
▶13 queue = deque([start])
▶14 while queue:
▶15 u = queue.popleft()
▶16 for v in adj[u]:
▶17 if color[v] == color[u]:
▶18 return False
▶19 if color[v] == 0:
▶20 color[v] = -color[u]
▶21 queue.append(v)
▶22 return True
dislikegroup Agroup B1234queueemptygroup Aemptygroup Bemptynobody placed yet
people4vertices
dislikes3edges
Model. Each dislike is an edge that must join two different groups. The question becomes: can the graph be coloured with two colours so that every edge has one end of each? Choosing one person's group forces everyone connected to them.
dislikegroup Agroup B1234queue1group A1group Bemptystart 1 → group A
start1new component
color[1]A
Nobody is placed yet, so the choice is free: put 1 in group A. Swapping A and B everywhere would give an equally good answer.
dislikegroup Agroup B1234queue23group A1group B23pop 1 · 2, 3 → group B
u1group A
painted2, 3now group B
queue[2, 3]
Everyone 1 dislikes must be in the other group, so 2, 3 are forced into group B. They go on the queue, because their own dislikes are forced in turn.
dislikegroup Agroup B1234queue34group A14group B23pop 2 · 4 → group A
u2group B
painted4now group A
queue[3, 4]
Everyone 2 dislikes must be in the other group, so 4 is forced into group A. It goes on the queue, because its own dislikes are forced in turn.
dislikegroup Agroup B1234queue4group A14group B23pop 3 · nothing new forced
u3group B
paintednoneno new forced choices
queue[4]
Every person 3 dislikes already sits in the other group, which is exactly what is required. Nothing new is forced.
dislikegroup Agroup B1234queueemptygroup A14group B23pop 4 · nothing new forced
u4group A
paintednoneno new forced choices
queue[]
Every person 4 dislikes already sits in the other group, which is exactly what is required. Nothing new is forced.
dislikegroup Agroup B1234queueemptygroup A14group B23every dislike crosses A–B → true
resulttruethe dislike graph is bipartite
Possible. Every person has a group and every dislike joins A to B. The two colours are the two groups. Each person was queued once and each dislike checked from both ends: O(n + m).
05

DFS colouring

paint(u, c) gives person u colour c, then visits every neighbour: a neighbour with colour c is a clash, and an uncoloured one is painted -c recursively.

▶1class Solution:
▶2 def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
▶3 sys.setrecursionlimit(10000)
▶4 adj = [[] for _ in range(n + 1)]
▶5 for a, b in dislikes:
▶6 adj[a].append(b)
▶7 adj[b].append(a)
▶8 color = [0] * (n + 1)
▶9 
▶10 def paint(u: int, c: int) -> bool:
▶11 color[u] = c
▶12 for v in adj[u]:
▶13 if color[v] == c:
▶14 return False
▶15 if color[v] == 0 and not paint(v, -c):
▶16 return False
▶17 return True
▶18 
▶19 return all(color[u] or paint(u, 1) for u in range(1, n + 1))
06

Common pitfalls

Only colouring from person 1

✗ Wrong
color[1] = 1
queue = deque([1])
...
✓ Right
for start in range(1, n + 1):
    if color[start] == 0:
        # BFS from start

The dislike graph can be disconnected. With n = 5 and dislikes [[1,2], [3,4], [4,5], [3,5]], BFS from 1 never reaches the triangle 3, 4, 5 and wrongly returns true.

Adding each dislike in one direction only

✗ Wrong
for a, b in dislikes:
    adj[a].append(b)
✓ Right
for a, b in dislikes:
    adj[a].append(b)
    adj[b].append(a)

Dislike is mutual, but the graph would only know one direction. For n = 3, [[1,2], [3,1]], person 3 is started on its own, gets colour 1 without ever seeing that 1 already has colour 1, and the check then reports a clash: false, although {1} and {2, 3} work.

Rejecting every cycle

✗ Wrong
if color[v] != 0:
    return False  # any revisit
✓ Right
if color[v] == color[u]:
    return False  # same group only

Meeting an already-coloured person is normal. Four people who dislike in a ring, [[1,2], [2,3], [3,4], [4,1]], split fine into {1, 3} and {2, 4}. Only an edge between two people of the same colour, which comes from an odd cycle, makes it impossible.

07

Edge cases

People numbered from 1

Arrays have size n + 1 and index 0 is unused, so person n has a slot.

08

Complexity

Time
O(n + m)
Space
O(n + m)
m is the number of dislikes. Each person is coloured and dequeued once, and each dislike is checked twice (once from each end). The adjacency list holds 2m entries.
09

Possible Bipartition FAQ

How do you solve possible bipartition?
  • Model: people are vertices, dislikes are undirected edges.
  • Goal: 2-colour the graph so no edge joins two equal colours.
  • BFS: from every uncoloured person, colour it 1; give each uncoloured neighbour the opposite colour and queue it.
  • Fail: if a neighbour already has the same colour, return false.
  • Complexity: O(n + m) time and space.
Is possible bipartition the same as Is Graph Bipartite?

Yes, apart from the input format. LeetCode 785 gives the adjacency list directly with vertices from 0; the possible bipartition LeetCode problem (886) gives an edge list of dislikes with people from 1. The colouring algorithm is identical.