0


数据结构学习笔记 2-3 并查集(Union-find)与 LeetCode真题(Java)

喜欢该类型文章可以给博主点个关注,博主会持续输出此类型的文章,知识点很全面,再加上LeetCode的真题练习,每一个LeetCode题解我都写了详细注释,比较适合新手入门数据结构与算法,后续也会更新进阶的文章。
课件参考—开课吧《门徒计划》

2-3 并查集(Union-find)及经典问题

并查集基础知识

并查集是一个在学完树形结构之后,在树形结构基础之上的一个图论的数据结构。

并查集解决的问题:

  • 连通性问题,可以看成数学方面的集合问题(概念上)➢ 判断一个元素在哪个集合里面 判断两个元素是否在同一个集合中

一开始

    0
   
   
    −
   
   
    9
   
  
  
   0-9
  
 
0−9 中每一个数字都是一个集合,首先我们把 

 
  
   
    4
   
  
  
   4
  
 
4 和 

 
  
   
    3
   
  
  
   3
  
 
3 之间画一条线,此时 

 
  
   
    4
   
  
  
   4
  
 
4 和 

 
  
   
    3
   
  
  
   3
  
 
3 就是一个集合,我们再把 

 
  
   
    3
   
  
  
   3
  
 
3 和 

 
  
   
    8
   
  
  
   8
  
 
8 连起来,原本只是一个 

 
  
   
    3
   
  
  
   3
  
 
3 和 

 
  
   
    8
   
  
  
   8
  
 
8 的集合,现在变成了 

 
  
   
    4
   
  
  
   4
  
 
4 、

 
  
   
    3
   
  
  
   3
  
 
3、

 
  
   
    8
   
  
  
   8
  
 
8 的一个集合,所以得出连通性的问题是具有传递性的,比如 

 
  
   
    A
   
  
  
   A
  
 
A 和 

 
  
   
    B
   
  
  
   B
  
 
B 在一个集合,

 
  
   
    B
   
  
  
   B
  
 
B 和 

 
  
   
    C
   
  
  
   C
  
 
C 也在一个集合,那么 

 
  
   
    A
   
  
  
   A
  
 
A 和 

 
  
   
    C
   
  
  
   C
  
 
C 肯定也在一个集合。

image-20220417192030456

那么我们怎么判断这些数是否在一个集合中呢?如下:

image-20220513200451863

Quick-Find算法

如下这两张gif动图就很好的诠释了上面的连通性问题,一开始每个数字都为不同的颜色:

    [
   
   
    4
   
   
    −
   
   
    3
   
   
    ]
   
   
     
   
   
    [
   
   
    3
   
   
    −
   
   
    8
   
   
    ]
   
   
     
   
   
    [
   
   
    6
   
   
    −
   
   
    5
   
   
    ]
   
   
     
   
   
    [
   
   
    9
   
   
    −
   
   
    4
   
   
    ]
   
  
  
   [4-3] \ [3-8]\ [6-5]\ [9-4]
  
 
[4−3] [3−8] [6−5] [9−4]

右面的

    3
   
   
    、
   
   
    4
   
   
    、
   
   
    8
   
   
    、
   
   
    9
   
  
  
   3、4、8、9
  
 
3、4、8、9 已经连通了,此时没有必要再连一次 

 
  
   
    [
   
   
    8
   
   
    −
   
   
    9
   
   
    ]
   
  
  
   [8-9]
  
 
[8−9] 了。
gif

1

    [
   
   
    2
   
   
    −
   
   
    1
   
   
    ]
   
   
     
   
   
    [
   
   
    5
   
   
    −
   
   
    0
   
   
    ]
   
   
     
   
   
    [
   
   
    7
   
   
    −
   
   
    2
   
   
    ]
   
   
     
   
   
    [
   
   
    6
   
   
    −
   
   
    1
   
   
    ]
   
  
  
   [2-1] \ [5-0]\ [7-2]\ [6-1]
  
 
[2−1] [5−0] [7−2] [6−1]

左面的

    0
   
   
    、
   
   
    1
   
   
    、
   
   
    2
   
   
    、
   
   
    5
   
   
    、
   
   
    6
   
   
    、
   
   
    7
   
  
  
   0、1、2、5、6、7
  
 
0、1、2、5、6、7 也已经连通了,

 
  
   
    [
   
   
    1
   
   
    −
   
   
    0
   
   
    ]
   
  
  
   [1-0]
  
 
[1−0] 和 

 
  
   
    [
   
   
    6
   
   
    −
   
   
    7
   
   
    ]
   
  
  
   [6-7]
  
 
[6−7] 也没有必要再连一次了。
gif

2

所有具有这样的连接关系,我们都可以把它类比成一棵树,像最后合并完成时,可以看做是这样的两棵树:

image-20220513201743698

这两棵树放在一起,也可以叫做 “森林”,所以并查集也可以说是一种基于森林的算法。

代码实现染色

这是并查集最野蛮,最低效的办法,通过染色的方式来实现集合的维护关系。

// Quick-FindclassUnionFind{int[] color;// 每个点的颜色int n;// 有多少个点publicUnionFind(int n){this.n = n;
        color =newint[n];for(int i =0; i < n; i++){
            color[i]= i;// 给每一个数字都赋予一个不同的颜色}}// 查询publicintfind(int x){return color[x];}// 合并publicvoidmerge(int x,int y){// 如果颜色相同 则说明已经在一个集合中if(color[x]== color[y])return;int cy = color[y];// 记录y的颜色for(int i =0; i < n; i++){// 这样我们就把所有跟y颜色一样的数 变为跟x一样的颜色if(color[i]== cy) color[i]= color[x];}}}

image-20220513210642976

所以接下来我们要想办法优化合并操作。

Quick-Union算法

朴素并查集

image-20220513211419404

根节点的颜色是什么,我们整棵树的颜色就是什么,我们查询的时候只需要找到查询的节点所在的那棵树的根节点。

所以并查集本质是一个不记录子节点,只记录根节点的树。

假设一个子节点

    8
   
  
  
   8
  
 
8 的父节点是 

 
  
   
    3
   
  
  
   3
  
 
3,我们可以创建一个 

 
  
   
    f
   
   
    a
   
   
    t
   
   
    h
   
   
    e
   
   
    r
   
   
    [
   
   
    8
   
   
    ]
   
   
    =
   
   
    3
   
  
  
   father[8]=3
  
 
father[8]=3,这样就对我们之前的 

 
  
   
    c
   
   
    o
   
   
    l
   
   
    o
   
   
    r
   
  
  
   color
  
 
color 数组做了一个优化,我们把单纯的染色关系变成了指向关系。

代码实现

// Quick-UnionclassUnionFind{int[] fa;// 记录祖宗节点int n;// 有多少个点publicUnionFind(int n){this.n = n;
        fa =newint[n];for(int i =0; i < n; i++){
            fa[i]= i;// 一开始所有节点的父节点都是它自己}}// 查询 从下往上找祖宗节点 一定是以链表的方式 所以可以用递归/while()循环实现publicintfind(int x){if(fa[x]== x)return x;returnfind(fa[x]);}// 合并publicvoidmerge(int x,int y){int fx =find(x);// 拿到x的祖宗节点int fy =find(y);// 拿到y的祖宗节点if(fx == fy)return;
        fa[fx]= fy;// 使fx成为fy的子树}}

image-20220513211451550

虽然我们合并的操作优化为

    O
   
   
    (
   
   
    N
   
   
    )
   
   
    →
   
   
    O
   
   
    (
   
   
    T
   
   
    r
   
   
    e
   
   
    e
   
   
     
   
   
    H
   
   
    i
   
   
    g
   
   
    h
   
   
    t
   
   
    )
   
  
  
   O(N)→O(Tree\ Hight)
  
 
O(N)→O(Tree Hight),但我们的查询操作由 

 
  
   
    O
   
   
    (
   
   
    1
   
   
    )
   
   
    →
   
   
    O
   
   
    (
   
   
    T
   
   
    r
   
   
    e
   
   
    e
   
   
     
   
   
    H
   
   
    i
   
   
    g
   
   
    h
   
   
    t
   
   
    )
   
  
  
   O(1)→O(Tree\ Hight)
  
 
O(1)→O(Tree Hight) 增加了,所以这个算法也是有问题的。

所以我们要在

    Q
   
   
    u
   
   
    i
   
   
    c
   
   
    k
   
   
    −
   
   
    U
   
   
    n
   
   
    i
   
   
    o
   
   
    n
   
  
  
   Quick-Union
  
 
Quick−Union 基础上再做优化。

树越高,时间复杂度就越大,那么我们应该考虑,是按照 节点数量 还是 树的高度合并呢?

是树高深的树接到浅的上面 还是 节点多的树接在少的树上面?我们需要盖棺定论。

image-20220513213457574

不难看出,一个树越高,它的平均查找次数也就越大,时间复杂度也就越大。

接下来我们对比一下两颗抽象的树:

image-20220513214400416

此时我们发现,两棵树合并时,哪棵树的节点少,谁就做儿子。

所以我们可以加一个记录节点个数的数组。

Weighted Quick-Union算法

并查集——按秩合并(维护size的并查集)

image-20220513211833066

按秩合并 代码实现

// Weighted Quick-UnionclassUnionFind{int[] fa;// 记录祖宗节点int[] size;// 记录每个集合的个数int n;// 有多少个点publicUnionFind(int n){this.n = n;
        fa =newint[n];
        size =newint[n];for(int i =0; i < n; i++){
            fa[i]= i;// 一开始所有节点的父节点都是它自己
            size[i]=1;}}// 查询 从下往上找祖宗节点 一定是以链表的方式 所以可以用递归/while()循环实现publicintfind(int x){if(fa[x]== x)return x;returnfind(fa[x]);}// 合并publicvoidmerge(int x,int y){int fx =find(x);// 拿到x的祖宗节点int fy =find(y);// 拿到y的祖宗节点if(fx == fy)return;// 谁小 谁做子树if(size[fx]< size[fy]){
            fa[fx]= fy;
            size[fy]+= size[fx];}else{
            fa[fy]= fx;
            size[fx]+= size[fy];}}}

image-20220513215552832

    F
   
   
    i
   
   
    n
   
   
    d
   
  
  
   Find
  
 
Find 和 

 
  
   
    U
   
   
    n
   
   
    i
   
   
    o
   
   
    n
   
  
  
   Union
  
 
Union 操作都优化为了 

 
  
   
    O
   
   
    (
   
   
    l
   
   
    o
   
   
    g
   
   
    N
   
   
    )
   
  
  
   O(logN)
  
 
O(logN) 。

我们来进行最终优化——路径压缩。

Weighted Quick-Union With Path Compression算法

路径压缩(维护到祖宗节点距离的并查集)

把我们整个链扁平化:

  • 当找到该节点的祖宗节点时,我们就直接将该节点指向祖宗节点,进行路径压缩,让最终的树高只有2

image-20220513223303290

路径压缩 代码实现

// Weighted Quick-Union With Path CompressionclassUnionFind{int[] fa;// 记录祖宗节点int n;// 有多少个点publicUnionFind(int n){this.n = n;
        fa =newint[n];for(int i =0; i < n; i++){
            fa[i]= i;// 一开始所有节点的父节点都是它自己}}// 查询 从下往上找祖宗节点 一定是以链表的方式 所以可以用递归/while()循环实现publicintfind(int x){if(fa[x]== x)return x;int root =find(fa[x]);
        fa[x]= root;// 路径压缩 => 直接让当前节点指向祖宗节点return root;/*
        简化版:
        if (fa[x] != x) fa[x] = find(fa[x]);
        return fa[x];
        */}// 合并publicvoidmerge(int x,int y){int fx =find(x);// 拿到x的祖宗节点int fy =find(y);// 拿到y的祖宗节点if(fx == fy)return;
        fa[fx]= fy;// 这里谁做子树就随意了}}
    F
   
   
    i
   
   
    n
   
   
    d
   
  
  
   Find
  
 
Find 和 

 
  
   
    U
   
   
    n
   
   
    i
   
   
    o
   
   
    n
   
  
  
   Union
  
 
Union 操作近似的都优化为了 

 
  
   
    O
   
   
    (
   
   
    1
   
   
    )
   
  
  
   O(1)
  
 
O(1) 。

并查集总结

image-20220513223916059

在使用的时候尽量使用我们第四个最优化的算法,因为前三个都比较慢。

推荐阅读:

  1. 并查集(Union-Find) 算法介绍
  2. 并查集(Union-Find) 应用举例 — 基础篇

image-20220513224712790

LeetCode真题

经典面试题—并查集基础

LeetCode547. 省份数量

难度:

mid

读完题发现,这道题描述的就是一个并查集的经典问题,题中的省份就是一个并查集的集合。

本题涉及到了图论的

邻接矩阵

,其实只是涉及到了二维数组的下标存储。

在一个

    n
   
   
    ×
   
   
    n
   
  
  
   n×n
  
 
n×n 的矩阵中,

 
  
   
    i
   
   
    s
   
   
    C
   
   
    o
   
   
    n
   
   
    n
   
   
    e
   
   
    c
   
   
    t
   
   
    e
   
   
    d
   
   
    [
   
   
    i
   
   
    ]
   
   
    [
   
   
    j
   
   
    ]
   
   
    =
   
   
    1
   
  
  
   isConnected[i][j] = 1
  
 
isConnected[i][j]=1 则代表第 

 
  
   
    i
   
  
  
   i
  
 
i 个城市和第 

 
  
   
    j
   
  
  
   j
  
 
j 个城市相连,分析第一个样例:

image-20220514133457556

转换为邻接矩阵后发现

    [
   
   
    1
   
   
    ,
   
   
    2
   
   
    ]
   
  
  
   [1, 2]
  
 
[1,2] 和 

 
  
   
    [
   
   
    2
   
   
    ,
   
   
    1
   
   
    ]
   
  
  
   [2, 1]
  
 
[2,1] 两个城市是连通的,

 
  
   
    1
   
  
  
   1
  
 
1 和 

 
  
   
    2
   
  
  
   2
  
 
2 构成了 一个集合,

 
  
   
    3
   
  
  
   3
  
 
3 自己单独构成了一个集合, 所以返回 

 
  
   
    2
   
  
  
   2
  
 
2 。

我们可以忽略对角线的

    1
   
  
  
   1
  
 
1,因为对角线代表的两个城市都只是自己。

LeetCode题解:代码实现


LeetCode200. 岛屿数量

难度:

mid

一个并查集的裸题,就是求在一个矩阵中,有几个

    1
   
  
  
   1
  
 
1 的集合。

image-20220514141600090

具体细节实现看代码,有两个小trick:

① 二维数组中的查找优化:只需判断该点左和上方向的数。

② 我们怎么合并上下左右为1的数字呢?利用二维数组的下标 给每一个数字一个编号。

LeetCode题解:代码实现


LeetCode990. 等式方程的可满足性

难度:

mid

这个题如果没学过并查集,想必很难做出来,但当你学过并查集之后,就很容易想到这就是一个维护集合的问题。

两个字母

    a
   
   
     
   
   
    b
   
  
  
   a\ b
  
 
a b,中间的符号要么是 

 
  
   
    !
   
   
    =
   
  
  
   !=
  
 
!= 要么是 

 
  
   
    =
   
   
    =
   
  
  
   ==
  
 
==;

如果是

    =
   
   
    =
   
  
  
   ==
  
 
== 就相当于是一次合并操作,我们就将 

 
  
   
    a
   
  
  
   a
  
 
a 和 

 
  
   
    b
   
  
  
   b
  
 
b 放入一个集合当中;

如果是

    !
   
   
    =
   
  
  
   \ !=
  
 
 != 就相当于是一次查询操作,查询 

 
  
   
    a
   
  
  
   a
  
 
a 和 

 
  
   
    b
   
  
  
   b
  
 
b 是否在一个集合,返回 

 
  
   
    t
   
   
    r
   
   
    u
   
   
    e
   
   
    /
   
   
    f
   
   
    a
   
   
    l
   
   
    s
   
   
    e
   
  
  
   true/false
  
 
true/false。

LeetCode题解:代码实现


经典面试题—并查集进阶

LeetCode684. 冗余连接

难度:

mid

一棵树

    n
   
  
  
   n
  
 
n 个节点,

 
  
   
    n
   
   
    −
   
   
    1
   
  
  
   n-1
  
 
n−1 条边,题中的树:

 
  
   
    n
   
  
  
   n
  
 
n 个节点,

 
  
   
    n
   
  
  
   n
  
 
n 条边,所以多了这一条边,肯定会给这棵加一个环。

所以本题最终目的就是

判环

,哪条边会使这棵树出现环,这个边 就是我们可以删去的边。

就是在添加时判断是否已经连通过了。

LeetCode题解:代码实现


LeetCode1319. 连通网络的操作次数

难度:

mid
    c
   
   
    o
   
   
    n
   
   
    n
   
   
    e
   
   
    c
   
   
    t
   
   
    i
   
   
    o
   
   
    n
   
   
    s
   
   
    [
   
   
    i
   
   
    ]
   
   
    =
   
   
    [
   
   
    a
   
   
    ,
   
   
    b
   
   
    ]
   
  
  
   connections[i] = [a,b]
  
 
connections[i]=[a,b] 代表连接了 

 
  
   
    a
   
  
  
   a
  
 
a 和 

 
  
   
    b
   
  
  
   b
  
 
b,给了我们初始的连接方式,我们可以断开任意的线并连接到新的计算机中,求最小操作次数。

本质上还是求我们集合的数量。

    3
   
  
  
   3
  
 
3 个集合我们需要操作 

 
  
   
    2
   
  
  
   2
  
 
2 次,

 
  
   
    2
   
  
  
   2
  
 
2 个集合我们需要操作 

 
  
   
    1
   
  
  
   1
  
 
1 次,用并查集求解就简单了许多。

**

     a
    
    
     n
    
    
     s
    
    
     =
    
    
     集
    
    
     合
    
    
     数
    
    
     量
    
    
     −
    
    
     1
    
   
   
    ans = 集合数量-1
   
  
 ans=集合数量−1**

LeetCode题解:代码实现


LeetCode128. 最长连续序列

难度:

mid

这道题用并查集来做的话,我们可以把连续的数字放到一个集合中,最后返回一个最大的集合。

我们怎么把连续的数放到一个集合呢?

示例1中,对于

    2
   
  
  
   2
  
 
2 这个数,它的相邻数字是 

 
  
   
    1
   
  
  
   1
  
 
1 和 

 
  
   
    3
   
  
  
   3
  
 
3,我们只需要把 

 
  
   
    1
   
  
  
   1
  
 
1 和 

 
  
   
    3
   
  
  
   3
  
 
3 的下标存入我们的集合即可,也就是 

 
  
   
    3
   
  
  
   3
  
 
3 和 

 
  
   
    4
   
  
  
   4
  
 
4,我们可以用哈希做映射。

image-20220514172729251

对于封装的并查集类,我们需要加一个

    c
   
   
    n
   
   
    t
   
   
    [
   
   
    ]
   
  
  
   cnt[]
  
 
cnt[] 来记录我们每个集合的大小。

LeetCode题解:代码实现


LeetCode947. 移除最多的同行或同列石头

难度:

mid

我们先把所有点都画出来,然后将所有同一行,同一列的点连起来,形成一个集合:

image-20220514181328312

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

所以我们可以将一个集合中 移动到只剩

    1
   
  
  
   1
  
 
1 个石子。

**所以本题本质:

最大移除石子数 = 原有石头数 - 剩余石头数量 = 合并次数

**

LeetCode题解:代码实现


LeetCode1202. 交换字符串中的元素

难度:

mid

注意题中一句话:你可以 任意多次交换

pairs

中任意一对索引处的字符。

只要把这句话理解了,那么你就会发现这道题可以用并查集求解。

示例1表述的并不是很清楚,我们看示例2:

image-20220514192646989

这个

    p
   
   
    a
   
   
    i
   
   
    r
   
   
    s
   
  
  
   pairs
  
 
pairs 代表着 

 
  
   
    0
   
   
     
   
   
    1
   
   
     
   
   
    2
   
   
     
   
   
    3
   
  
  
   0\ 1\ 2\ 3
  
 
0 1 2 3 都在一个集合中,所以 

 
  
   
    d
   
   
    c
   
   
    a
   
   
    b
   
  
  
   dcab
  
 
dcab 可以进行任意次数的交换而达到字典序最小的 

 
  
   
    a
   
   
    b
   
   
    c
   
   
    d
   
  
  
   abcd
  
 
abcd。

示例3:

image-20220514192827287

这个

    p
   
   
    a
   
   
    i
   
   
    r
   
   
    s
   
  
  
   pairs
  
 
pairs 代表着 

 
  
   
    0
   
   
     
   
   
    1
   
   
     
   
   
    2
   
  
  
   0\ 1\ 2
  
 
0 1 2 都在一个集合中,所以 

 
  
   
    c
   
   
    b
   
   
    a
   
  
  
   cba
  
 
cba 可以进行任意次数的交换而达到字典序最小的 

 
  
   
    a
   
   
    b
   
   
    c
   
  
  
   abc
  
 
abc。

所以上面的那句话的意思就是:我们可以 在一个集合中 任意交换集合中的元素 使得该集合的字典序最小。

我们可以对每个集合排序,那还有别的办法吗?

不难想到用来做,我们可以维护

    n
   
  
  
   n
  
 
n 个小顶堆,一个并查集的集合对应着一个堆,每次把堆顶元素 

 
  
   
    p
   
   
    o
   
   
    p
   
   
    (
   
   
    )
   
  
  
   pop()
  
 
pop() 掉。

上篇文章讲了堆:数据结构学习笔记 2-2 堆(Heap)与优先队列与 LeetCode真题(Java)

LeetCode题解:代码实现


经典面试题—附加选做题

LeetCode765. 情侣牵手

难度:

hard
    N
   
  
  
   N
  
 
N 对情侣一开始可能并没有坐到一起,我们要将其中的两个人交换,使得每对情侣坐到一起,求最少交换次数。

毕竟是一个

    h
   
   
    a
   
   
    r
   
   
    d
   
  
  
   hard
  
 
hard 题,我们找一下规律:

假如现在有

    3
   
  
  
   3
  
 
3 对情侣:


 
  
   
    (
   
   
    0
   
   
    ,
   
   
    1
   
   
    )
   
  
  
   (0,1)
  
 
(0,1)


 
  
   
    (
   
   
    2
   
   
    ,
   
   
    3
   
   
    )
   
  
  
   (2,3)
  
 
(2,3)


 
  
   
    (
   
   
    4
   
   
    ,
   
   
    5
   
   
    )
   
  
  
   (4,5)
  
 
(4,5)

那么在计算机中:

    0
   
   
    /
   
   
    2
   
   
    =
   
   
    0
   
   
    ,
   
   
    1
   
   
    /
   
   
    2
   
   
    =
   
   
    0
   
  
  
   0/2=0,1/2=0
  
 
0/2=0,1/2=0


 
  
   
    2
   
   
    /
   
   
    2
   
   
    =
   
   
    1
   
   
    ,
   
   
    3
   
   
    /
   
   
    2
   
   
    =
   
   
    1
   
  
  
   2/2=1,3/2=1
  
 
2/2=1,3/2=1


 
  
   
    4
   
   
    /
   
   
    2
   
   
    =
   
   
    2
   
   
    ,
   
   
    5
   
   
    /
   
   
    2
   
   
    =
   
   
    2
   
  
  
   4/2=2,5/2=2
  
 
4/2=2,5/2=2

所以:

    N
   
  
  
   N
  
 
N 对情侣我们就维护 

 
  
   
    N
   
  
  
   N
  
 
N 个集合,这 

 
  
   
    3
   
  
  
   3
  
 
3 对情侣的集合编号分别为 

 
  
   
    0
   
   
     
   
   
    1
   
   
     
   
   
    2
   
  
  
   0\ 1\ 2
  
 
0 1 2。

如示例1所示:

    r
   
   
    o
   
   
    w
   
   
    =
   
   
    [
   
   
    0
   
   
    ,
   
   
    2
   
   
    ,
   
   
    1
   
   
    ,
   
   
    3
   
   
    ]
   
  
  
   row=[0,2,1,3]
  
 
row=[0,2,1,3],他们两对情侣就属于下图左边的情况,

 
  
   
    0
   
  
  
   0
  
 
0 集合 和 

 
  
   
    1
   
  
  
   1
  
 
1 集合有相交,所以此时只有 

 
  
   
    1
   
  
  
   1
  
 
1 个集合,最少操次数就是 

 
  
   
    N
   
   
    −
   
   
    1
   
   
    =
   
   
    1
   
  
  
   N-1=1
  
 
N−1=1。

如果

    r
   
   
    o
   
   
    w
   
   
    =
   
   
    [
   
   
    0
   
   
    ,
   
   
    1
   
   
    ,
   
   
    2
   
   
    ,
   
   
    3
   
   
    ]
   
  
  
   row = [0, 1, 2, 3]
  
 
row=[0,1,2,3],他们两对情侣就属于下图右边的情况,

 
  
   
    0
   
  
  
   0
  
 
0 集合 和 

 
  
   
    1
   
  
  
   1
  
 
1 集合无相交,此时有 

 
  
   
    2
   
  
  
   2
  
 
2 个集合,最少操次数就是 

 
  
   
    N
   
   
    −
   
   
    2
   
   
    =
   
   
    0
   
  
  
   N-2=0
  
 
N−2=0,两对情侣本身就是挨在一起的,无需交换。

image-20220514211241873

那么对于三对情侣:

    r
   
   
    o
   
   
    w
   
   
    =
   
   
    [
   
   
    0
   
   
    ,
   
   
    2
   
   
    ,
   
   
    1
   
   
    ,
   
   
    4
   
   
    ,
   
   
    5
   
   
    ,
   
   
    3
   
   
    ]
   
  
  
   row=[0, 2, 1, 4, 5, 3]
  
 
row=[0,2,1,4,5,3],是否也成立呢?


 
  
   
    N
   
   
    −
   
   
    1
   
   
    =
   
   
    2
   
  
  
   N - 1=2
  
 
N−1=2,最少交换次数的确是 

 
  
   
    2
   
  
  
   2
  
 
2 次。

image-20220514211808926

**所以得出:

     最
    
    
     少
    
    
     交
    
    
     换
    
    
     次
    
    
     数
    
    
     =
    
    
     情
    
    
     侣
    
    
     对
    
    
     数
    
    
     −
    
    
     集
    
    
     合
    
    
     数
    
    
      
    
    
     =
    
    
     >
    
    
      
    
    
     a
    
    
     n
    
    
     s
    
    
     =
    
    
     N
    
    
     −
    
    
     c
    
    
     n
    
    
     t
    
   
   
    最少交换次数=情侣对数-集合数\ =>\ ans = N - cnt
   
  
 最少交换次数=情侣对数−集合数 => ans=N−cnt**

找到了规律那么代码实现就很简单了。

LeetCode题解:代码实现


LeetCode685. 冗余连接 II

难度:

hard

在冗余连接Ⅰ中 是无向图,在冗余连接Ⅱ 中是有向图。

有向树:

  1. 无环
  2. 入度只为 0 0 0 或 1 1 1(只有根节点的入度为 0 0 0 其余节点的入度为 1 1 1)

我们可以用并查集来判断有向树是否有环,用一个

    v
   
   
    i
   
   
    s
   
   
    [
   
   
    ]
   
  
  
   vis[]
  
 
vis[] 数组来记录每个节点的入度是多少。

所以我们的操作就是:删边建树

因为也有一条附加边,我们遍历每条边,判断删掉它之后 是否符合我们有向树的定义即可。

LeetCode题解:代码实现


总结

本章重点是并查集基础知识的推导过程,但是在使用并查集时我们都要使用最终优化版本——路径压缩

目前Java类库并没有实现并查集类,所以每次我们做题需要自己实现一个并查集;

实现也非常简单,一个find查询方法,一个merge合并方法;当然,有时候还需要计数、记录状态,只需要在我们原有的数据结构添加所需要的功能即可。

其实我们发现,并查集使用起来非常简单,难就难在后面的有些题,我们看不出来可以用并查集求解,当你发现这道题可以用并查集求解,那这道题的代码实现就很轻松了。


本文转载自: https://blog.csdn.net/weixin_53407527/article/details/124780373
版权归原作者 小成同学_ 所有, 如有侵权,请联系我们删除。

“数据结构学习笔记 2-3 并查集(Union-find)与 LeetCode真题(Java)”的评论:

还没有评论