英文原文
There are n
cities. Some of them are connected, while some are not. If city a
is connected directly with city b
, and city b
is connected directly with city c
, then city a
is connected indirectly with city c
.
A province is a group of directly or indirectly connected cities and no other cities outside of the group.
You are given an n x n
matrix isConnected
where isConnected[i][j] = 1
if the ith
city and the jth
city are directly connected, and isConnected[i][j] = 0
otherwise.
Return the total number of provinces.
Example 1:
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]] Output: 2
Example 2:
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]] Output: 3
Constraints:
1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j]
is1
or0
.isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]
中文题目
有 n
个城市,其中一些彼此相连,另一些没有相连。如果城市 a
与城市 b
直接相连,且城市 b
与城市 c
直接相连,那么城市 a
与城市 c
间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n
的矩阵 isConnected
,其中 isConnected[i][j] = 1
表示第 i
个城市和第 j
个城市直接相连,而 isConnected[i][j] = 0
表示二者不直接相连。
返回矩阵中 省份 的数量。
示例 1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] 输出:2
示例 2:
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] 输出:3
提示:
1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j]
为1
或0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]
通过代码
高赞题解
前言
连续两天都是并查集的题目,借此题来详细刨析一下
基本概念
并查集是一种数据结构
并查集这三个字,一个字代表一个意思。
并(Union),代表合并
查(Find),代表查找
集(Set),代表这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素
并查集的典型应用是有关连通分量的问题
并查集解决单个问题(添加,合并,查找)的时间复杂度都是$O(1)$
因此,并查集可以应用到在线算法中
并查集的实现
数据结构
并查集跟树有些类似,只不过她跟树是相反的。在树这个数据结构里面,每个节点会记录它的子节点。在并查集里,每个节点会记录它的父节点。
class UnionFind:
def __init__(self):
"""
记录每个节点的父节点
"""
self.father = {}
class UnionFind{
private:
// 记录父节点
unordered_map<int,int> father;
};
class UnionFind {
private Map<Integer,Integer> father;
}
可以看到,如果节点是相互连通的(从一个节点可以到达另一个节点),那么他们在同一棵树里,或者说在同一个集合里,或者说他们的祖先是相同的。
初始化
当把一个新节点添加到并查集中,它的父节点应该为空
def add(self,x):
"""
添加新节点
"""
if x not in self.father:
self.father[x] = None
void add(int x){
if(!father.count(x)){
father[x] = -1;
}
}
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
}
}
<,>
合并两个节点
如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。这里究竟把谁当做父节点一般是没有区别的。
def merge(self,x,y,val):
"""
合并两个节点
"""
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
void merge(int x,int y){
int root_x = find(x);
int root_y = find(y);
if(root_x != root_y){
father[root_x] = root_y;
}
}
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY){
father.put(rootX,rootY);
}
}
<,,>
两节点是否连通
我们判断两个节点是否处于同一个连通分量的时候,就需要判断它们的祖先是否相同
def is_connected(self,x,y):
"""
判断两节点是否相连
"""
return self.find(x) == self.find(y)
bool is_connected(int x,int y){
return find(x) == find(y);
}
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
查找祖先
查找祖先的方法是:如果节点的父节点不为空,那就不断迭代。
def find(self,x):
"""
查找根节点
"""
root = x
while self.father[root] != None:
root = self.father[root]
return root
int find(int x){
int root = x;
while(father[root] != -1){
root = father[root];
}
return root;
}
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
return root;
}
<,,,>
这里有一个优化的点:如果我们树很深,比如说退化成链表,那么每次查询的效率都会非常低。所以我们要做一下路径压缩。也就是把树的深度固定为二。
这么做可行的原因是,并查集只是记录了节点之间的连通关系,而节点相互连通只需要有一个相同的祖先就可以了。
路径压缩可以用递归,也可以迭代。这里用迭代的方法。
<,,,,,,,,,,,,,>
def find(self,x):
"""
查找根节点
路径压缩
"""
root = x
while self.father[root] != None:
root = self.father[root]
# 路径压缩
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
int find(int x){
int root = x;
while(father[root] != -1){
root = father[root];
}
// 路径压缩
while(x != root){
int original_father = father[x];
father[x] = root;
x = original_father;
}
return root;
}
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
while(x != root){
int original_father = father.get(x);
father.put(x,root);
x = original_father;
}
return root;
}
路径压缩的时间复杂度为$O(\log^*n)$
$\log^*n$ 表示 n 取多少次$\log_2n$并向下取整以后 变成 1
可以认为$O(\log^n) = O(1)$,因为$\log^2^{65536} = 5$,而$2^{65536}$是一个天文数字。这个时间复杂度当成结论记下就可以。
完整模板
class UnionFind:
def __init__(self):
"""
记录每个节点的父节点
"""
self.father = {}
def find(self,x):
"""
查找根节点
路径压缩
"""
root = x
while self.father[root] != None:
root = self.father[root]
# 路径压缩
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
def merge(self,x,y,val):
"""
合并两个节点
"""
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
def is_connected(self,x,y):
"""
判断两节点是否相连
"""
return self.find(x) == self.find(y)
def add(self,x):
"""
添加新节点
"""
if x not in self.father:
self.father[x] = None
class UnionFind{
public:
int find(int x){
int root = x;
while(father[root] != -1){
root = father[root];
}
while(x != root){
int original_father = father[x];
father[x] = root;
x = original_father;
}
return root;
}
bool is_connected(int x,int y){
return find(x) == find(y);
}
void merge(int x,int y){
int root_x = find(x);
int root_y = find(y);
if(root_x != root_y){
father[root_x] = root_y;
}
}
void add(int x){
if(!father.count(x)){
father[x] = -1;
}
}
private:
// 记录父节点
unordered_map<int,int> father;
};
class UnionFind {
private Map<Integer,Integer> father;
public UnionFind() {
father = new HashMap<Integer,Integer>();
}
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
}
}
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY){
father.put(rootX,rootY);
}
}
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
while(x != root){
int original_father = father.get(x);
father.put(x,root);
x = original_father;
}
return root;
}
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
}
以上就是并查集的基本模板,根据不同的题目要求进行对应的添加即可。
今天的题目
今天的题目就是在考察连通分量的数目,所以我们要在模板中额外添加一个变量去跟踪集合的数量(有多少棵树)。
初始化的时候把集合数量加一
合并的时候让集合数量减一
class UnionFind:
def __init__(self):
self.father = {}
# 额外记录集合的数量
self.num_of_sets = 0
def find(self,x):
root = x
while self.father[root] != None:
root = self.father[root]
while x != root:
original_father = self.father[x]
self.father[x] = root
x = original_father
return root
def merge(self,x,y):
root_x,root_y = self.find(x),self.find(y)
if root_x != root_y:
self.father[root_x] = root_y
# 集合的数量-1
self.num_of_sets -= 1
def add(self,x):
if x not in self.father:
self.father[x] = None
# 集合的数量+1
self.num_of_sets += 1
class Solution:
def findCircleNum(self, M: List[List[int]]) -> int:
uf = UnionFind()
for i in range(len(M)):
uf.add(i)
for j in range(i):
if M[i][j]:
uf.merge(i,j)
return uf.num_of_sets
class UnionFind{
public:
int find(int x){
int root = x;
while(father[root] != -1){
root = father[root];
}
while(x != root){
int original_father = father[x];
father[x] = root;
x = original_father;
}
return root;
}
bool is_connected(int x,int y){
return find(x) == find(y);
}
void merge(int x,int y){
int root_x = find(x);
int root_y = find(y);
if(root_x != root_y){
father[root_x] = root_y;
num_of_sets--;
}
}
void add(int x){
if(!father.count(x)){
father[x] = -1;
num_of_sets++;
}
}
int get_num_of_sets(){
return num_of_sets;
}
private:
// 记录父节点
unordered_map<int,int> father;
// 记录集合数量
int num_of_sets = 0;
};
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
UnionFind uf;
for(int i = 0;i < isConnected.size();i++){
uf.add(i);
for(int j = 0;j < i;j++){
if(isConnected[i][j]){
uf.merge(i,j);
}
}
}
return uf.get_num_of_sets();
}
};
class UnionFind {
// 记录父节点
private Map<Integer,Integer> father;
// 记录集合的数量
private int numOfSets = 0;
public UnionFind() {
father = new HashMap<Integer,Integer>();
numOfSets = 0;
}
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
numOfSets++;
}
}
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY){
father.put(rootX,rootY);
numOfSets--;
}
}
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
while(x != root){
int original_father = father.get(x);
father.put(x,root);
x = original_father;
}
return root;
}
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
public int getNumOfSets() {
return numOfSets;
}
}
class Solution {
public int findCircleNum(int[][] isConnected) {
UnionFind uf = new UnionFind();
for(int i = 0;i < isConnected.length;i++){
uf.add(i);
for(int j = 0;j < i;j++){
if(isConnected[i][j] == 1){
uf.merge(i,j);
}
}
}
return uf.getNumOfSets();
}
}
相关题目
模板题:
在线算法:
其他:
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
173475 | 280350 | 61.9% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|
相似题目
题目 | 难度 |
---|---|
无向图中连通分量的数目 | 中等 |
机器人能否返回原点 | 简单 |
句子相似性 | 简单 |
句子相似性 II | 中等 |
彼此熟识的最早时间 | 中等 |