并查集-合集

并查集-合集

做题链接 : https://leetcode-cn.com/problems/satisfiability-of-equality-equations/solution/shi-yong-bing-cha-ji-chu-li-bu-xiang-jiao-ji-he-we/

1. Leetcode 947 - 移除最多的同行或同列石头

题目描述

  • n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。

  • 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。

  • 给你一个长度为 n 的数组stones,其中 stones[i] = [xi, yi]表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
示例 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] 是平面上唯一一块石头,所以不可以移除它。

思路 : 并查集

  • 把二维坐标平面上的石头想象成图的顶点,如果两个石头横坐标相同、或者纵坐标相同,在它们之间形成一条边。

mark

  • 根据可以移除石头的规则:如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。可以发现:一定可以把一个连通图里的所有顶点根据这个规则删到只剩下一个顶点。

    • 为什么这么说呢?既然这些顶点在一个连通图里,可以通过遍历的方式(深度优先遍历或者广度优先遍历)遍历到这个连通图的所有顶点。
    • 那么就可以按照遍历的方式 逆向 移除石头,最后只剩下一块石头。所以:最多可以移除的石头的个数 = 所有石头的个数 - 连通分量的个数
    mark

算法具体

  • 删到最后,留在图中的顶点一定位于不同的行和不同的列。因此,并查集里的元素是 描述「横坐标」和「纵坐标」的数值
  • 因此我们需要遍历数组 stones,将每个 stone 的横坐标和纵坐标在并查集中进行合并。理解合并的语义十分重要。

合并的语意

  • 「合并」的语义是:所有横坐标为 x 的石头和所有纵坐标为 y 的石头都属于同一个连通分量。

并查集里区别横纵坐标

mark

  • 然而会遇到这样一个问题:石头的位置是「有序数对(二维)」,并查集的底层是「一维数组」,我们在并查集里应该如何区分横纵坐标呢?
  • 例如:如果一块石头的坐标为 [3, 3] ,那么所有横坐标为 3 的石头和所有纵坐标为 3 的石头都在一个连通分量中,但是我们需要在并查集里区分「横坐标」和「纵坐标」,它们在并查集里不能相等
    • 根据题目的提示 0 <= x_i, y_i <= 10^40
    • 可以把其中一个坐标 映射 到另一个与 [0, 10000] 不重合的区间
    • 可以的做法是把横坐标全部减去 10000或者全部加上 10000 ,或者按位取反。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Solution {
public int removeStones(int[][] stones) {
UnionFind unionFind = new UnionFind();

// 查找每块石头
for(int[] stone:stones){
// 下面这三种写法任选其一
// unionFind.union(~stone[0], stone[1]);
// unionFind.union(stone[0] - 10000, stone[1]);
unionFind.union(stone[0] + 10000, stone[1]);
}

// 返回顶点数 - 连通分量的个数
return stones.length - unionFind.getCount();
}

// 并查集
private class UnionFind{
// 维护的底层数组 : map代替
private Map<Integer,Integer> parent;
private int count;

public UnionFind(){
this.parent = new HashMap<>();
this.count = 0;
}

public int getCount(){
return count;
}

// 1. 查找
public int find(int x){
// 1.1 如果x还未出现
if(!parent.containsKey(x)){
parent.put(x,x);
count++;
}
// 1.2 查x的根分量
if(x != parent.get(x)){
parent.put(x,find(parent.get(x)));
}
return parent.get(x);
}

// 2. 合并
public void union(int x,int y){
int rootX = find(x);
int rootY = find(y);
if(rootX == rootY){
return;
}

parent.put(rootX,rootY);
count--;
}
}
}

mark

复杂度分析

  • 时间复杂度 : O(nlog(A)) , A是数组stones里横纵坐标不同值的总数
  • 空间复杂度 : O(A) ,并查集底层哈希表的长度为A
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信