这是一个图论问题,首先按照题意构建图。
石头作为顶点。
如果两个石头位于同一行或同一列,则认为这两个石头之间有一条边,即这两个石头位于同一个连通分支之内。由题中删除石头的规则可知,实际上就是不断删除连通分支之内的顶点。而每个连通分支最后都会剩下一个顶点。所以,可以移除的最大的石头数 = 石头总数 - 连通分支数。
所以题目就转化为两步,首先要构建图,然后再计算图中的连通分支数。
计算连通分支数一般有两类方法,一是使用DFS或BFS之类的搜索方法,二是使用并查集的方法。这里考虑并查集的方法
并查集
并查集回答的是图的连通性问题,可以快速得出图中的连通分支数,但不回答同属于一个连通分支的顶点是如何连接的问题,本题只需要计算图中的连通分支的个数,所以比较适合用并查集。
并查集的底层是一维的,所以要把二维的坐标映射到一维。因为x的范围是[0, 10000],所以将y坐标整体加上10000,就可以将y映射到另一个集合。
又因为,只要x,y坐标有一个相同的顶点都属于相同的集合,所以可以将每个顶点的x,y都合并起来,这样就可以简便地构成了连通分支,省去了建图的步骤。同时,构建并查集的时候应当维护当前连通分支的数量。如果有新的顶点就加一,如果合并了一个顶点就减一。
代码如下,并查集基于集合的大小(size)进行了优化(另一种方法是基于树高(rank)进行优化):
class Solution {
public:
Solution() {
connectedComponentCount = 0;
}
int findf(int x) {
if (!f.count(x)) {
f[x] = x;
setSize[x] = 1;
connectedComponentCount++;
}
return f[x] == x ? x : f[x] = findf(f[x]);
}
void unionSet(int x, int y) {
int fx = findf(x), fy = findf(y);
if (fx == fy) return;
if (setSize[fx] < setSize[fy]) swap(fx, fy);
f[fy] = fx;
setSize[fx] += setSize[fy];
connectedComponentCount--;
}
int removeStones(vector<vector<int>>& stones) {
int n = stones.size();
for (int i = 0; i < n; ++i) {
// 把每个顶点的x和y坐标看作是同级别的两个数,将一个顶点的x和y坐标合并,这样和该顶点具有相同x坐标和y坐标的点都会被合并到同一个集合中
// 合并到不同集合中的顶点一定是具有
unionSet(stones[i][0], stones[i][1] + 10000);
}
return n - connectedComponentCount;
}
private:
unordered_map<int, int> f, setSize; // 使用哈希表为底层数据结构实现父亲数组, setSize是每个联通分支的顶点数
int connectedComponentCount;
};