947.移除最多的同行或同列石头——并查集

在看题之前,先补充并查集的知识:

并查集

并查集(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();
    }
};

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值