Medium
题目描述
在一个二维平面上,我们将 n 块石头放置在一些整数坐标点上。每个坐标点上最多只能有一块石头。
如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。
给你一个长度为 n 的数组 stones,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石头的最大数量。
示例 1:
输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
输出:5
解释:一种移除 5 块石头的方法如下所示:
1. 移除石头 [2,2] ,因为它和 [2,1] 同行。
2. 移除石头 [2,1] ,因为它和 [0,1] 同列。
3. 移除石头 [1,2] ,因为它和 [1,0] 同行。
4. 移除石头 [1,0] ,因为它和 [0,0] 同列。
5. 移除石头 [0,1] ,因为它和 [0,0] 同行。
石头 [0,0] 不能移除,因为它没有与另一块石头同行/列。
示例 2:
输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
输出:3
解释:一种移除 3 块石头的方法如下所示:
1. 移除石头 [2,2] ,因为它和 [2,0] 同行。
2. 移除石头 [2,0] ,因为它和 [0,0] 同列。
3. 移除石头 [0,2] ,因为它和 [0,0] 同行。
石头 [0,0] 和 [1,1] 不能移除,因为它们没有与另一块石头同行/列。
示例 3:
输入:stones = [[0,0]]
输出:0
解释:[0,0] 是平面上唯一的石头,所以不能移除它。
提示:
- 1 <= stones.length <= 1000
- 0 <= xi, yi <= 10^4
- 没有两块石头位于同一个坐标点上。
解题思路
这道题的核心思想是通过并查集(Union-Find)或DFS找到连通分量的数量。
解题思路:
连通性分析:如果两块石头在同一行或同一列,它们就是连通的。我们需要找到所有连通的石头组。
关键观察:在一个连通分量中,最多只能保留一块石头,其余都可以移除。比如如果一个连通分量有 k 块石头,那么可以移除 k-1 块。
两种解法:
- 并查集法:为每个行和列分配唯一的ID,将石头所在的行列进行合并操作
- DFS法:构建图,对每个未访问的石头进行DFS,统计连通分量
并查集优化:为了区分行列坐标,可以将列坐标加上偏移量(如10001)来避免冲突。
计算结果:总石头数减去连通分量数量,就是可以移除的最大石头数。
推荐使用并查集解法,代码更简洁且效率较高。
代码实现
class Solution {
public:
vector<int> parent;
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int px = find(x), py = find(y);
if (px != py) {
parent[px] = py;
}
}
int removeStones(vector<vector<int>>& stones) {
int n = stones.size();
parent.resize(20002);
for (int i = 0; i < 20002; i++) {
parent[i] = i;
}
for (auto& stone : stones) {
unite(stone[0], stone[1] + 10001);
}
unordered_set<int> components;
for (auto& stone : stones) {
components.insert(find(stone[0]));
}
return n - components.size();
}
};
class Solution:
def removeStones(self, stones: List[List[int]]) -> int:
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def unite(x, y):
px, py = find(x), find(y)
if px != py:
parent[px] = py
n = len(stones)
parent = list(range(20002))
for x, y in stones:
unite(x, y + 10001)
components = set()
for x, y in stones:
components.add(find(x))
return n - len(components)
public class Solution {
private int[] parent;
private int Find(int x) {
if (parent[x] != x) {
parent[x] = Find(parent[x]);
}
return parent[x];
}
private void Unite(int x, int y) {
int px = Find(x), py = Find(y);
if (px != py) {
parent[px] = py;
}
}
public int RemoveStones(int[][] stones) {
int n = stones.Length;
parent = new int[20002];
for (int i = 0; i < 20002; i++) {
parent[i] = i;
}
foreach (var stone in stones) {
Unite(stone[0], stone[1] + 10001);
}
var components = new HashSet<int>();
foreach (var stone in stones) {
components.Add(Find(stone[0]));
}
return n - components.Count;
}
}
var removeStones = function(stones) {
const parent = Array.from({length: 20002}, (_, i) => i);
function find(x) {
if (parent[x] !== x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
function unite(x, y) {
const px = find(x), py = find(y);
if (px !== py) {
parent[px] = py;
}
}
const n = stones.length;
for (const [x, y] of stones) {
unite(x, y + 10001);
}
const components = new Set();
for (const [x, y] of stones) {
components.add(find(x));
}
return n - components.size;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n × α(n)) | n为石头数量,α为反阿克曼函数,近似O(n) |
| 空间复杂度 | O(1) | 并查集数组大小固定为20002 |