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找到连通分量的数量。

解题思路:

  1. 连通性分析:如果两块石头在同一行或同一列,它们就是连通的。我们需要找到所有连通的石头组。

  2. 关键观察:在一个连通分量中,最多只能保留一块石头,其余都可以移除。比如如果一个连通分量有 k 块石头,那么可以移除 k-1 块。

  3. 两种解法

    • 并查集法:为每个行和列分配唯一的ID,将石头所在的行列进行合并操作
    • DFS法:构建图,对每个未访问的石头进行DFS,统计连通分量
  4. 并查集优化:为了区分行列坐标,可以将列坐标加上偏移量(如10001)来避免冲突。

  5. 计算结果:总石头数减去连通分量数量,就是可以移除的最大石头数。

推荐使用并查集解法,代码更简洁且效率较高。

代码实现

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

相关题目