什么是并查集:并查集(Union-find Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。
并查集代码封装如下
//定义个保存父节点索引的数组,
let fa = [];
//给所有节点赋默认值
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
}
}
//查询当前根元素,当根元素等于自己时就是根节点l
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
//将a根节点挂在b根节点下,这样a和b就有共同根节点
let merge = function(a,b){
fa[get(a)] = get(b);
}
547. 省份数量
有 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
解题思路:本体可以利用并查集将所有相连的城市放到一个根节点下,然后循环isConnected有几个根节点,有几个就代表有几个省份:代码如下:
var findCircleNum = function(isConnected) {
//因为是全局变量需要重置
fa = [];
//重置当前树父节点索引
unionSet(isConnected.length);
for(let i = 0; i < isConnected.length; i ++){
for(let j = 0; j < isConnected[i].length; j ++){
//如果isConnected[i][j] == 1就带边 i 和 j相连
if(isConnected[i][j]) merge(i,j);
}
}
ans = 0;
//循环父节点,判断几个有子节点,有的话就是一颗单独的数,也就是一个单独的省份
for(let i = 0; i < isConnected.length; i ++){
//如果get(i) == i 就代表i下面有子字节,i就是根节点,所以加一
if(i == get(i)) ans ++;
}
return ans
};
//以下代码上面已经介绍过
let fa = [];
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
}
console.log(fa)
}
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
let merge = function(a,b){
console.log(get(a),get(b))
fa[get(a)] = get(b);
}
200. 岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
解题思路:本题可以将二维数组转换成一维数组,一维数组组成是每个二维数组的索引,长度为,二维数组的长乘以宽,这题也可以并查集来解决,这题本意是想看将1连接起来,有几个独立的连线模块,如果当前节点是1,那么如果上面节点,和左边节点节点也是1的话就可以分别将这两个索引合并,为什么不判断右,因为当前节点结束,就会操作右节点,所有没必要,下面也是一样,代码如下:
var numIslands = function(grid) {
//重置全局变量
fa = [];
//获得数组长度
let n = grid.length;
//获得每行个数
let m = grid[0].length;
//给二维数组每个索引赋默认值
unionSet(n*m);
for(let i = 0; i < n; i ++){
for(let j = 0; j < m; j ++){
//当前节点为0不需要合并
if(grid[i][j] == '0') continue;
//判断当前节点是否存在上一个节点,如果存在并且等于1,就将当前节点索引个上面一个索引合并,find()是用来查找索引的
if(i > 0 && grid[i - 1][j] == '1') merge(find(i -1,j,m),find(i,j,m));
//判断左边是否可以合并
if(j > 0 && grid[i][j - 1] == '1') merge(find(i,j - 1,m),find(i,j,m));
}
}
let cnt = 0;
//循环遍历以索引组织的以为数组有几个根节点存在,存在的和就是本题所求
for(let i = 0; i < n; i ++){
for(let j = 0; j < m; j ++){
if(grid[i][j] == '0') continue ;
if(find(i,j,m) == get(find(i,j,m))) cnt ++;
}
}
return cnt;
};
// 计算索引
let find = function(i,j,m){
return i * m + j;
}
//定义个保存父节点索引的数组,
let fa = [];
//给所有节点赋默认值
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
}
}
//查询当前根元素,当根元素等于自己时就是根节点l
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
//将a根节点挂在b根节点下,这样a和b就有共同根节点
let merge = function(a,b){
fa[get(a)] = get(b);
}
}
990. 等式方程的可满足性
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b" 或 "a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。
示例 1:
输入:["a==b","b!=a"]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。
示例 2:
输入: ["b==a","a==b"]
输出: true
解释: 我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。
示例 3:
输入: ["a==b","b==c","a==c"]
输出: true
示例 4:
输入: ["a==b","b!=c","c==a"]
输出: false
示例 5:
输入: ["c==c","b==d","x!=z"]
输出: true
解题思路:这题我们可以先将相等的两个字母合并,然后再判断不相等的两个数组是否都存在相等的数组中,如果存在那就返回false,不相等就是成立的,具体实现需要将字母转换成从0开始的数字,因为我们封装的并查集默认的父节点是从0开始的数组,题目中说都是小写字母,所有默认的保存父节点数组的长度小于26代码如下:
var equationsPossible = function(equations) {
fa = [];
let n = equations;
//默认父节点数组索引
unionSet(26);
//将字母a转换成数字,a - a = 0; b - a = 1; c- 1 = 2;...
let aIndex = 'a'.charCodeAt(0);
for(let i = 0; i < n.length; i ++){
//如果是不等号不需要合并
if(n[i][1] == '!') continue;
//将相等的两个字母转换成数字,这里就表示两个是相等的,所有合并成一个数组
let a = n[i][0].charCodeAt(0) - aIndex;
let b = n[i][3].charCodeAt(0) - aIndex;
merge(a,b);
}
for(let i = 0; i < n.length; i ++){
if(n[i][1] == '=') continue;
let a = n[i][0].charCodeAt(0) - aIndex;
let b = n[i][3].charCodeAt(0) - aIndex;
//如果a b 同时存在相等的数组里,就不符合
if(get(a) == get(b)) return false;
}
return true;
};
//定义个保存父节点索引的数组,
let fa = [];
//给所有节点赋默认值
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
}
}
//查询当前根元素,当根元素等于自己时就是根节点l
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
//将a根节点挂在b根节点下,这样a和b就有共同根节点
let merge = function(a,b){
fa[get(a)] = get(b);
}
684. 冗余连接
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
示例 1:
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
示例 2:
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解题思路:这题的解题思路是,先判断当前的两个值是否存在同一个父节点,如果存在就是多余的操作,代码如下:
var findRedundantConnection = function(edges) {
fa = [];
unionSet(edges.length);
for(let i = 0; i < edges.length; i ++){
let a = edges[i][0];
let b = edges[i][1];
//判断当前两个值是否存在同一个根节点,如果存在就是多余才做,不存在就合并,这样大家就会在一个数组里,
if(get(a) == get(b)) return edges[i];
merge(a,b);
}
return [];
};
//定义个保存父节点索引的数组,
let fa = [];
//给所有节点赋默认值
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
}
}
//查询当前根元素,当根元素等于自己时就是根节点l
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
//将a根节点挂在b根节点下,这样a和b就有共同根节点
let merge = function(a,b){
fa[get(a)] = get(b);
}