移除最多的同行或同列石头 图解题解
这道题到底在问什么
- 输入
- stones=[[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
- 输出
- 5(6 颗石头连成 1 组,留 1 移 5)
最优解:一步一步想明白
- 3记住「行列当节点 → 每颗石头 union 它的行和列 → 答案 = 石头数 − 连通块数」,下面逐帧套它。
- 4先为用到的行、列各放一个节点:上排是第 0、1、2 行(R0、R1、R2),下排是第 0、1、2 列(C0、C1、C2)。此刻它们彼此独立,连通块数 = 6,相当于行列还没被任何石头牵连。
- 5并查集准备就绪:每个行/列节点的 parent 先指向自己。接下来按顺序扫每颗石头,把它的行节点和列节点并起来。
- 6第 1 颗石头 (0, 0):它占住第 0 行和第 0 列,所以要把行节点 R0 和列节点 C0 连起来(它俩高亮)。
- 7union(R0, C0):各自 find 出根后,把 R0 那组的根接到 C0 所在组的根上(union 永远是接到对方的根、不是接到某个普通节点),两组并成同一个连通块(同色)。连通块数减 1,现在是 5。
- 8第 2 颗石头 (0, 1):它占住第 0 行和第 1 列,所以要把行节点 R0 和列节点 C1 连起来(它俩高亮)。
- 9union(R0, C1):各自 find 出根后,把 R0 那组的根接到 C1 所在组的根上(union 永远是接到对方的根、不是接到某个普通节点),两组并成同一个连通块(同色)。连通块数减 1,现在是 4。
- 10第 3 颗石头 (1, 0):它占住第 1 行和第 0 列,所以要把行节点 R1 和列节点 C0 连起来(它俩高亮)。
- 11union(R1, C0):各自 find 出根后,把 R1 那组的根接到 C0 所在组的根上(union 永远是接到对方的根、不是接到某个普通节点),两组并成同一个连通块(同色)。连通块数减 1,现在是 3。
- 12第 4 颗石头 (1, 2):它占住第 1 行和第 2 列,所以要把行节点 R1 和列节点 C2 连起来(它俩高亮)。
- 13union(R1, C2):各自 find 出根后,把 R1 那组的根接到 C2 所在组的根上(union 永远是接到对方的根、不是接到某个普通节点),两组并成同一个连通块(同色)。连通块数减 1,现在是 2。
- 14第 5 颗石头 (2, 1):它占住第 2 行和第 1 列,所以要把行节点 R2 和列节点 C1 连起来(它俩高亮)。
- 15union(R2, C1):各自 find 出根后,把 R2 那组的根接到 C1 所在组的根上(union 永远是接到对方的根、不是接到某个普通节点),两组并成同一个连通块(同色)。连通块数减 1,现在是 1。
- 16第 6 颗石头 (2, 2):它占住第 2 行和第 2 列,所以要把行节点 R2 和列节点 C2 连起来(它俩高亮)。
- 17union(R2, C2):先各自 find 根,发现 R2 和 C2 已经在同一个连通块里了(被前面的石头经别的行列间接连通过)。这一步是个环、不改变结构,连通块数仍是 1。
- 18六颗石头都扫完了。看整张图:R0、R1、R2、C0、C1、C2 六个行列节点经一连串 union 全连到了一起,只剩 1 个连通块,说明这 6 颗石头其实是「互相牵连」的一大堆。
- 19数连通块的办法:对每个节点做一次 find 拿到它的根,根相同的就是同一块。下面逐个看,看它们是不是都指向同一个根。
- 20find(R0) 的根是 C2。这是目前见到的第 1 个不同的根。
- 21find(R1) 的根是 C2。和前面某个节点的根相同,归入同一连通块,不新增。
- 22find(R2) 的根是 C2。和前面某个节点的根相同,归入同一连通块,不新增。
- 23find(C0) 的根是 C2。和前面某个节点的根相同,归入同一连通块,不新增。
- 24find(C1) 的根是 C2。和前面某个节点的根相同,归入同一连通块,不新增。
- 25find(C2) 的根是 C2。和前面某个节点的根相同,归入同一连通块,不新增。
- 26数下来只有 1 个不同的根,也就是 1 个连通块。每个连通块里的石头互相同行或同列连着,可以一颗颗移到只剩 1 颗。所以最多移除 = 石头数 − 连通块数 = 6 − 1 = 5。
⚠️ 容易写错的地方
✗ 错:把石头本身两两连边
✓ 对:行、列各当节点,每石 union 行与列
两两判同行同列是 O(n²);用行列节点,每颗石头只做一次 union,既快又自然把同行/同列的石头并到一起
✗ 错:行键和列键用同一套整数
✓ 对:列号加偏移量(或用元组)与行区分
第 0 行和第 0 列若都写成 0 会被并错;加偏移量(c + 10001)或用 ("r"/"c", x) 元组才不冲突
✗ 错:直接返回连通块数
✓ 对:返回 石头数 − 连通块数
每个连通块能移到只剩 1 颗,移除数是「总数 − 留下的块数」,不是块数本身
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def removeStones(self, stones: List[List[int]]) -> int:
parent = {}
def find(x):
parent.setdefault(x, x)
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
for r, c in stones:
parent[find(('r', r))] = find(('c', c))
roots = {find(('r', r)) for r, _ in stones}
return len(stones) - len(roots)C++
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
unordered_map<int,int> parent;
int find(int x) {
if (!parent.count(x)) parent[x] = x;
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
public:
int removeStones(vector<vector<int>>& stones) {
parent.clear();
const int OFFSET = 10001;
for (auto &s : stones) parent[find(s[0])] = find(s[1] + OFFSET);
unordered_set<int> roots;
for (auto &s : stones) roots.insert(find(s[0]));
return stones.size() - roots.size();
}
};Java
import java.util.*;
class Solution {
Map<Integer, Integer> parent = new HashMap<>();
public int removeStones(int[][] stones) {
parent.clear();
int offset = 10001;
for (int[] s : stones) parent.put(find(s[0]), find(s[1] + offset));
Set<Integer> roots = new HashSet<>();
for (int[] s : stones) roots.add(find(s[0]));
return stones.length - roots.size();
}
private int find(int x) {
parent.putIfAbsent(x, x);
if (!parent.get(x).equals(x)) parent.put(x, find(parent.get(x)));
return parent.get(x);
}
}复杂度
时间
近似 O(n)
n 是石头数。每颗石头做一次 union(两次 find);本实现只用了路径压缩(没有按秩合并),单次操作均摊也已接近常数;统计根再扫一遍 n,合计近似线性
空间
O(n)
parent 哈希表最多存 2n 个行列节点(每颗石头一行一列),量级 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 移除最多的同行或同列石头 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么每个连通块一定能移到只剩 1 颗?+
在一个连通块里,总能找到一颗「与块内别的石头同行或同列」的石头先移走(按某种顺序,比如倒着做 DFS 序),移走它不影响其余石头仍连通;一直移到最后只剩 1 颗,它此时没有同行同列的伙伴、停。所以每块恰好留 1 颗。
能不能不用并查集,改用 DFS/BFS 求连通分量?+
可以。把石头建成图:同行或同列的石头之间连边,再数连通分量,答案同样是石头数减分量数。并查集的好处是边读边并、不用显式建图、代码更短;DFS/BFS 思路一致,只是要先建邻接关系。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 移除最多的同行或同列石头 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。