Leetcode 并查集详解

news2025/1/15 14:12:26

在一些应用的问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一格单元素集合,然后按一定顺序将属于同一组的元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的抽象数据类型称为并查集。

并查集

并查集(Disjoint-set Union)是一种常见的数据结构,用于维护一组不相交的集合,支持合并两个集合和查询某个元素所属的集合。并查集常用于解决图论、连通性问题和动态连通性问题等,具有较高的效率和易用性。

并查集的基本操作包括:

  1. 初始化:将每个元素初始化为一个独立的集合,即每个元素都是该集合的根节点
  2. 合并:将两个不相交的集合合并成一个集合,即将其中一个集合的根节点连接到另一个集合的根节点上
  3. 查找:查找某个元素所属的集合,即找到该元素所在集合的根节点

并查集的优点是实现简单,可以在近乎O(1)的时间复杂度内完成合并和查找操作,因此常用于需要频繁合并和查询集合的问题中。

在这里插入图片描述

并查集的实现

以下是使用Java语言实现并查集的示例代码:

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

	/**
	 * 合并两个集合,使用平衡再优化
	 */
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }

	/**
	 * 判断两个节点是否在同一个集合中
	 */
    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}

在这个示例代码中,我们使用两个数组parentrank来表示并查集。

  • parent[i]表示节点i的父节点,初始时每个节点的父节点都是它自己
  • rank[i]表示以节点i为根节点的子树的深度,初始时每个节点的深度都为0
  • find()方法用于查找某个节点所在的集合,它采用路径压缩的方式来优化查找效率。在查找节点x所在的集合时,如果节点x不是根节点,就将它的父节点设置为根节点,这样下次查找时就可以直接找到- 根节点,从而加速查找效率。
  • union()方法用于合并两个集合,它采用按秩合并(rank)的方式来优化合并效率。在合并两个集合时,先找到它们的根节点,如果两个根节点相同,则说明它们已经在同一个集合中,直接返回;否则,- 将深度较小的根节点连接到深度较大的根节点下面。
  • connected()方法用于判断两个节点是否在同一个集合中,它直接调用find()方法来查找它们所在的集合,并比较两个集合的根节点是否相同

通过这样的实现,我们可以在近乎O(1)的时间复杂度内完成并查集的合并和查找操作。

Leetcode 真题

最长连续序列

解题思路:构造并查集,获取集合中节点的最大深度

class Solution {
    public int longestConsecutive(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        UnionFind uf = new UnionFind(nums.length);
        for (int i = 0; i < nums.length; i++) {
            // 存在重复元素,跳过
            if (map.containsKey(nums[i])){
                continue;
            }
            if (map.containsKey(nums[i] - 1)) {
                uf.union(i, map.get(nums[i] - 1));
            }
            if (map.containsKey(nums[i] + 1)) {
                uf.union(i, map.get(nums[i] + 1));
            }
            map.put(nums[i], i);
        }
        return uf.getMaxConnectSize();
    }
}

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }

    public int getMaxConnectSize() {
        int maxSize = 0;
        for (int i = 0; i < parent.length; i++) {
            if (i == parent[i]) {
                maxSize = Math.max(maxSize, rank[i]);
            }
        }
        return maxSize;
    }
}

省份数量

解题思路:构造并查集,获取集合中父节点为自身的节点(独立区域)

class Solution {
    public int findCircleNum(int[][] isConnected) {
        UnionFind uf = new UnionFind(isConnected.length);
        for (int i = 0; i < isConnected.length; i++) {
            for (int j = i + 1; j < isConnected[i].length; j++) {
                if (isConnected[i][j] == 1) {
                    uf.union(i, j);
                }
            }
        }

        int count = 0;
        for (int i = 0; i < isConnected.length; i++) {
            if (uf.parent[i] == i) {
                count++;
            }
        }
        return count;
    }
}

class UnionFind {
    public int[] parent;
    public int[] rank;

    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }
}

冗余连接

解题思路:
遍历每一条边,对于每一条边的两个端点:

  • 如果它们已经在同一个集合中,说明在加入这条边之后会出现环,此时这条边就是需要删除的边
  • 否则将这两个端点加入同一个集合中
public int[] findRedundantConnection(int[][] edges) {
	int n = edges.length;
	int[] parent = new int[n + 1];
	for (int i = 1; i <= n; i++) {
		parent[i] = i;
	}
	int[] result = null;
	for (int[] edge : edges) {
		int u = edge[0], v = edge[1];
		int pu = find(parent, u), pv = find(parent, v);
		if (pu == pv) {
			// u和v已经在同一个集合中,说明加入这条边会出现环
			result = edge;
		} else {
			// 将u和v加入同一个集合中
			parent[pu] = pv;
		}
	}
	return result;
}

/**
 * 查找节点x所在集合的根节点(路径压缩)
 */
private int find(int[] parent, int x) {
	while (parent[x] != x) {
		parent[x] = parent[parent[x]];
		x = parent[x];
	}
	return x;
}

参考资料:

  1. 快速并查集(Java实现)
  2. 并查集

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【22】linux进阶——文本处理工具:cut、awk、sed

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

一段凄惨Android 面试经历分享,败在了项目架构原理上……

大家应该看过很多分享面试成功的经验&#xff0c;但根据幸存者偏差的理论&#xff0c;也许多看看别人面试失败在哪里&#xff0c;对自己才更有帮助。 这是一位网友分享的面试经历&#xff0c;他准备了3个月&#xff0c;刚刚参加完字节跳动的第三面&#xff0c;视频面&#xff…

数说故事联合中山大学国际关系学院共建「国关数据实验室」,深化数据科学与国际关系融合创新

4月9日&#xff0c;数说故事联合中山大学国际关系学院共建的「国关数据实验室」正式启动&#xff0c;此次强强联合是双方在国际关系领域的一项创新尝试&#xff0c;该实验室旨在整合数说故事和国际关系学院师生的资源优势&#xff0c;将数据科学与国际关系研究相结合&#xff0…

真实还原美团4面经历,低学历成功拿到20K Offer...

个人背景 如标题所示&#xff0c;我的个人背景非常简单&#xff0c;软件测试经验 1 年半&#xff0c;学历普通&#xff0c;2 本毕业后出来就一直在做功能测试&#xff0c;在公司每天重复的工作对我的技术提升并没有什么帮助&#xff0c;但小镇出来的我也深知自我努力的重要性&…

Binder 与 四大组件工作原理 其一

Binder Binder的组成结构 Binder的架构如图所示 ServiceManager负责把Binder Server注册到一个容器中。 我们可以这样理解Client、Server 、ServiceManager、Binder Driver之间的关系&#xff1a; 把ServiceManager比喻成电话局&#xff0c;存储着每个住宅的座机电话。张三给…

记录-Vue移动端日历设计与实现

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 工作中遇到一个需求是根据日历查看某一天/某一周/某一月的睡眠报告&#xff0c;但是找了好多日历组件都不是很符合需求&#xff0c;只好自己手写一个日历组件&#xff0c;顺便记录一下。 先看看UI给的…

linux文件编辑与编辑命令

文章目录 一、linux文件编辑与编辑命令总结 一、linux文件编辑与编辑命令 Linux mkdir命令:创建目录 Linux more命令:显示文本文件内容 Linux cat命令:连接文件并打印到标准输出设备上 Linux grep命令:检索文件内容 Linux rm命令:删除文件或目录 Linux touch命令:修改文件的时…

15款时间计划、任务管理APP/软件对比(团队\个人)

15 款不同类型的日程、任务管理应用&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.Todoist&#xff1b;4.Trello&#xff1b;5.Microsoft To Do&#xff1b;6.Asana&#xff1b;7.Google 任务&#xff1b;8.Notion&#xff1b;9.Monday.com&#xff1b;10.Teambi…

Matlab对日期变量和时间变量的管理

Matlab2012a内置了三个函数 datanumdatevecdatestr 靠这三个函数&#xff0c;可以基本实现日期变量和时间变量的管理。下面直接来看。 &#xff08;1&#xff09;datanum 这个函数用来将字符串&#xff0c;日期矢量转为通用日&#xff08;数值型&#xff09;。所谓的通用日…

js特殊对象 - RegExp对象(正则表达式)

1、概述 正则表达式用于定义一些字符串的规则&#xff0c;计算机可以根据正则表达式&#xff0c;来检查一个字符串是否符合规则&#xff0c;获取将字符串中符合规则的内容提取出来。 使用typeof检查正则对象&#xff0c;会返回object。 2、创建正则对象 2.1、使用对象创建 语法…

Java spring 注解 @PostConstruct 实战讲解

前言 在最近的学习中&#xff0c;发现了一个非常实用的注解 —— PostConstruct。通过学习了解&#xff0c;逐步发现它能帮助我更轻松的解决不少原本很复杂的问题。 下面&#xff0c;结合实例介绍 PostConstruct 注解的特性&#xff0c;因为PreDestroy基本用不到&#xff0c;所…

C++算法初级11——01背包问题(动态规划2)

C算法初级11——01背包问题&#xff08;动态规划2&#xff09; 文章目录 C算法初级11——01背包问题&#xff08;动态规划2&#xff09;问题引入0-1背包问题分析0-1背包问题的形式化分析优化 问题引入 辰辰采药 辰辰是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大…

Ubuntu开机自启动一些东西

有三种方式做开机自启动 目录 1.免除sudo密码 2.Startup 2.desktop 3.service 1.免除sudo密码 做完这一步你的所有sudo命令都不会再让你输密码了 如果你的开机自启动的东西需要sudo&#xff0c;那么这一步就是必须的&#xff0c;如果不需要sudo&#xff0c;那么这一步可…

Linux安装kubectl

前言 以下所有命令基于CentOS7.9系统&#xff0c;官方参考文档&#xff1a;> 文章最后附有一键安装的脚本&#xff0c;可以直接运行脚本进行安装 下载安装文件 1. 下载最新发行版 curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/st…

C嘎嘎~~[类 中篇]

类 中篇 6.类的实例化7.类对象模型8.this指针8.1this指针是什么8.2this指针的特性 6.类的实例化 什么叫类的 实例化?? 首先, 我们应该关注这个"实" — 实际存在的, 它的反义词是 “虚” — 不存在的. > 类中的成员变量是虚的(相当于声明), 在类外面创建的对象是…

《程序员面试金典(第6版)》面试题 16.06. 最小差(双指针,pair数据结构)

题目描述 给定两个整数数组a和b&#xff0c;计算具有最小差绝对值的一对数值&#xff08;每个数组中取一个值&#xff09;&#xff0c;并返回该对数值的差 示例&#xff1a; 输入&#xff1a;{1, 3, 15, 11, 2}, {23, 127, 235, 19, 8}输出&#xff1a;3&#xff0c;即数值对(…

Power BI动态日期轴方法总结

趋势&#xff0c;应该是我们做可视化时最熟悉的一个词了&#xff0c;看趋势自然离不开日期&#xff0c;年度趋势&#xff0c;月趋势&#xff0c;周趋势等等。Power BI中我们可以借助于计算表&#xff0c;计算组&#xff0c;字段参数来实现动态实时轴的效果。 计算表实现动态日…

Node.js--基础

一、Node.js是什么 Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. 1、特性 Node.js 可以解析JS代码&#xff08;没有浏览器安全级别的限制&#xff09;提供很多系统级别的API&#xff0c;如&#xff1a; 文件的读写 (File System)进程的管理 …

每日一个小技巧:1分钟告诉你文字转图片的方法有哪些

在数字时代&#xff0c;信息传递快速便捷&#xff0c;但文字在传递中却显得单调乏味&#xff0c;难以吸引人们的眼球。为了解决这个问题&#xff0c;越来越多的人开始寻找方法将文字转化为图片。文字转图片不仅能够让文字更具视觉冲击力&#xff0c;还能够在社交媒体、广告宣传…

Nginx常见应用场景

文章目录 场景一&#xff1a;代理静态文件场景二&#xff1a;代理服务器 本教程讲述 Nginx 的常见应用场景。内容接上文&#xff1a;Nginx基本配置。 前提&#xff1a;假设我们已经安装好了 Nginx&#xff0c;并启动成功。 场景一&#xff1a;代理静态文件 Nginx 一个常用的功…