在看题之前,先补充并查集的知识:
并查集
并查集(Union-find Sets)是一种非常精巧和使用的数据结构,主要用于处理一些不相交集合的合并问题,一些常见的用途有求连通子图,求最小生成树的Kruskal算法和最近公共祖先(LCA)等
并查集主要操作:
1.初始化; 2.查询find;3.合并union
-
1.初始化
int fa[MAX];
void init(int n){
for(int i = 0;i <= n;i++){
fa[i] = i; //初始化将自己的父节点设置成自己
}
}
-
2.查询
int find(int x){
if(x != fa[x]) return find(fa[x]);
return fa[x];
}
或者
int find(int x){
if(x == fa[x]) return x;
return find(fa[x]);
}
-
3.合并
void union(int i,int j){
int rootx = find(x);
int rooty = find(y);
fa[rooty] = rootx;
}
不过这种没有任何优化的并查集,比较简单,但是效率很低:
-
问题1:当合并两个节点时,没有任何判断,便直接将rootx设置成了rooty的父节点,假如rooty的叶子节点深度比rootx的叶子节点深度大呢?此时树的深度会持续增加,造成后续的子节点查询时间长
-
问题2:在寻找某个节点的根节点过程中,没有对其父节点和祖父节点进行任何操作,假如该节点被合并很多次,每次都经过父节点,祖父节点层层寻找,造成不必要时间浪费
//注意,此代码并不能使得所有的元素都直接指向根节点,仍然存在间接的指向,但大部分都已指向
class Djset{
public:
vector<int> parent; // 记录节点的根
vector<int> rank; //记录根节点的深度
Djset(int n):parent(vector<int>(n)),rank(vector<int>(n)){
for(int i = 0;i < n;i++){
parent[i] = i;
}
}
int find(x){
//压缩方式,直接指向根节点
if (x != parent[x]){
parent[x] = find(parent[x]);
}
return parent[x];
}
void merge(int x,int y){
int rootx = find(x);
int rooty = find(y);
if(rootx != rooty){
//按深度合并
if(rank[rootx] < rank[rooty]){
swap(rootx,rooty);
}
parent[rooty] = rootx;
if(rank[rootx] == rank[rooty]) rank[rootx] += 1
}
}
}
题目
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] 不能移除,因为它们没有与另一块石头同行/列。
思路
观察题目,可以发现两个连接在一起,则能删除其中一个;若是有三个,则能删除其中两个;依次类推,若是有n个连接在一起,则其中的n-1个其实都是能够删除的,那么相当于找连通的图的个数,然后返回总数减掉这个值(每个连通图最后都只剩一个)
python代码:
class djset(object):
def __init__(self,n):
self.parent = [i for i in range(n)]
self.rank = [0 for _ in range(n)]
self.count = n
def find(self,x):
#路劲压缩
if (x != self.parent[x]):
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def merge(self,x,y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
if self.rank[rootx] < self.rank[rooty]:
rootx,rooty = rooty,rootx
self.parent[rooty] = rootx
if self.rank[rootx] == self.rank[rooty]:
self.rank[rootx] += 1
self.count -= 1
def get_count(self):
return self.count
class Solution(object):
def removeStones(self, stones):
n = len(stones)
dj = djset(n)
for i in range(n):
for j in range(i+1,n):
if stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]:
dj.merge(i,j)
return n - dj.get_count()
C++代码:
class Djset{
public:
vector<int> parent;
vector<int> rank;
int count;
Djset(int n):parent(vector<int>(n)),rank(vector<int>(n)),count(n) {
for(int i = 0;i < n;i++){
parent[i] = i;
}
}
int find(int x){
if(x != parent[x]){
//路径压缩
parent[x] = find(parent[x]);
}
return parent[x];
}
void merge(int x,int y){
int rootx = find(x);
int rooty = find(y);
if(rootx != rooty){
if(rank[rootx] < rank[rooty]){
swap(rootx,rooty);
}
parent[rooty] = rootx;
if(rank[rootx] == rank[rooty]) rank[rootx] += 1;
count--;
}
}
int get_count(){
return count;
}
};
class Solution {
public:
int removeStones(vector<vector<int>>& stones) {
int n = stones.size();
Djset dj(n);
for(int i = 0;i < n;i++){
for(int j = i+1;j < n;j++){
if(stones[i][0] == stones[j][0] || stones[i][1] == stones[j][1])
dj.merge(i,j);
}
}
return n - dj.get_count();
}
};