可能的二分法 图解题解
这道题到底在问什么
- 输入
- n=4, dislikes=[[1,2],[1,3],[2,4]]
- 输出
- true(组1 {1,4} / 组2 {2,3})
- 输入
- n=3, dislikes=[[1,2],[2,3],[1,3]]
- 输出
- false(三人两两讨厌,奇环)
最优解:一步一步想明白
- 3记住「建图 → BFS 染两色、相邻强制异色、撞同色就 false」,下面逐帧套它。
- 4先把讨厌关系画成图:1 讨厌 2、1 讨厌 3、2 讨厌 4,连成三条边。此刻所有人都灰着、还没分组。
- 5建好邻接表,记下每个人讨厌哪些人:1 讨厌 2、3;2 讨厌 1、4;3 讨厌 1;4 讨厌 2。color 数组全 0,表示还没人分组。
- 6从还没染色的 1 号开始,把它放进组1(第一种颜色),并放进队列。一个连通块从这里开始扩散。
- 7从队首取出 1 号(组1),看它讨厌的人:2、3。这些人都必须和 1 不同组。
- 8顺着 1→2 这条讨厌边看 2 号:它现在是「未分组」。还没分组,得给它定一组。
- 92 号还没分组。1 讨厌 2,所以把 2 染成和 1 相反的 组2,并入队等待处理。
- 10顺着 1→3 这条讨厌边看 3 号:它现在是「未分组」。还没分组,得给它定一组。
- 113 号还没分组。1 讨厌 3,所以把 3 染成和 1 相反的 组2,并入队等待处理。
- 12从队首取出 2 号(组2),看它讨厌的人:1、4。这些人都必须和 2 不同组。
- 13顺着 2→1 这条讨厌边看 1 号:它现在是「组1」。已经分过组了,核对一下会不会和 2 撞同组。
- 141 号已经在 组1,和 2(组2)正好不同组,这对讨厌关系没问题,继续。
- 15顺着 2→4 这条讨厌边看 4 号:它现在是「未分组」。还没分组,得给它定一组。
- 164 号还没分组。2 讨厌 4,所以把 4 染成和 2 相反的 组1,并入队等待处理。
- 17从队首取出 3 号(组2),看它讨厌的人:1。这些人都必须和 3 不同组。
- 18顺着 3→1 这条讨厌边看 1 号:它现在是「组1」。已经分过组了,核对一下会不会和 3 撞同组。
- 191 号已经在 组1,和 3(组2)正好不同组,这对讨厌关系没问题,继续。
- 20从队首取出 4 号(组1),看它讨厌的人:2。这些人都必须和 4 不同组。
- 21顺着 4→2 这条讨厌边看 2 号:它现在是「组2」。已经分过组了,核对一下会不会和 4 撞同组。
- 222 号已经在 组2,和 4(组1)正好不同组,这对讨厌关系没问题,继续。
- 23队列空了,所有人都染过色、全程没碰到任何「讨厌却同组」的冲突。分组成功:组1 {1, 4}、组2 {2, 3},返回 true。回头看,就是建图后 BFS 二染色、相邻强制异色,撞同色才失败。
⚠️ 容易写错的地方
✗ 错:只染包含某个点的一块
✓ 对:外层遍历每个人,没染色的都要当新起点 BFS
讨厌图可能分成几个互不相连的块,漏染别的块会得出错误结论
✗ 错:只看 v 未染色的情况
✓ 对:还要查 v 已染色且和 u 同组 → 立即 false
冲突正是发生在「邻居已染色、却和自己同色」时,这一支是判 false 的关键
✗ 错:以为有环就不能二分
✓ 对:只有奇数长度的环才不能二分
偶环可以交替染色、仍是二分图;能否二染色等价于「没有奇环」
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import deque
class Solution:
def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
g = [[] for _ in range(n + 1)]
for a, b in dislikes:
g[a].append(b); g[b].append(a)
color = [0] * (n + 1)
for i in range(1, n + 1):
if color[i]:
continue
color[i] = 1
q = deque([i])
while q:
u = q.popleft()
for v in g[u]:
if color[v] == color[u]:
return False
if color[v] == 0:
color[v] = -color[u]
q.append(v)
return TrueC++
#include <queue>
#include <vector>
using namespace std;
class Solution {
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
vector<vector<int>> g(n + 1);
for (auto &e : dislikes) { g[e[0]].push_back(e[1]); g[e[1]].push_back(e[0]); }
vector<int> color(n + 1);
for (int i = 1; i <= n; ++i) if (!color[i]) {
queue<int> q; q.push(i); color[i] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) {
if (color[v] == color[u]) return false;
if (!color[v]) { color[v] = -color[u]; q.push(v); }
}
}
}
return true;
}
};Java
import java.util.*;
class Solution {
public boolean possibleBipartition(int n, int[][] dislikes) {
List<Integer>[] g = new ArrayList[n + 1];
for (int i = 1; i <= n; i++) g[i] = new ArrayList<>();
for (int[] e : dislikes) { g[e[0]].add(e[1]); g[e[1]].add(e[0]); }
int[] color = new int[n + 1];
Queue<Integer> q = new ArrayDeque<>();
for (int i = 1; i <= n; i++) if (color[i] == 0) {
color[i] = 1; q.offer(i);
while (!q.isEmpty()) {
int u = q.poll();
for (int v : g[u]) {
if (color[v] == color[u]) return false;
if (color[v] == 0) { color[v] = -color[u]; q.offer(v); }
}
}
}
return true;
}
}复杂度
时间
O(n + e)
建图遍历所有讨厌边 e;BFS 每个人入队出队各一次、每条边被两端各看一次,合计 O(n + e),n 是人数、e 是讨厌对数
空间
O(n + e)
邻接表存全部边 O(n + e);color 数组、队列各 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 可能的二分法 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么这题等价于判二分图?+
把人当点、讨厌当边,「分两组使讨厌的人不同组」就是「给图二染色使相邻异色」,这正是二分图的定义。而一张图能二染色当且仅当它不含奇数长度的环,所以本题也可以说成「判断讨厌图有没有奇环」。
用并查集能做吗?+
能。带权或扩展域并查集:把每个人 x 拆成「x 在组A」和「x 在组B」两个点,对每条讨厌边 (a,b) 合并「a在A 与 b在B」「a在B 与 b在A」;若发现 a 的两个域被并到一起就矛盾、返回 false。本质和二染色一样,只是换了实现。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 可能的二分法 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。