并查集代码:
并查集的思想是用一个数组表示了整片森林(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);
}
128. 最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入: nums = [0,3,7,2,5,8,4,6,0,1]
输出: 9
解题思路:O(n)就是尽量减少循环操作,最好是只循环一遍,这题的解题思路,就是判断当前节点前一位和后一位数字是否存在,存在就合并,比如 2 的前一位只能是1,后一位只能是3,满足这种要求就能连接,然后记录有几个连接的数组,选出最长的数组就是这题答案,代码如下:
var longestConsecutive = function(nums) {
//如果数组不存在就是0,如果只有一个就是1
if(nums.length <= 1) return nums.length;
//重置全局变量
fa = [],cnt = [];
//设置默认父节点数组
unionSet(nums.length);
//给所有节点赋索引值,后面操作并查集操作的是当前值的索引
let p = {};
for(let i = 0; i < nums.length; i ++){
p[nums[i]] = i;
}
//判断当前值是否存在前后值,存在就合并索引
for(let i = 0; i < nums.length; i ++){
let a = nums[i] - 1;
let b = nums[i];
let c = nums[i] + 1;
if(p[a] >= 0) merge(p[a],p[b]);
if(p[c] >= 0) merge(p[b],p[c]);
}
//将父节点数组按照降序排列,结果就去数组第一位
let cnt2 = cnt.sort(function(a,b){
return b - a;
})
return cnt2[0];
};
let fa = [],cnt = [];
//给所有节点赋默认值
let unionSet = function(n){
for(let i = 0; i < n; i ++){
fa[i] = i;
//默认每个节点的数量是1
cnt[i] = 1;
}
}
//查询当前根元素,当根元素等于自己时就是根节点l
let get = function(x){
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
//将a根节点挂在b根节点下,这样a和b就有共同根节点
let merge = function(a,b){
//节点存在不用操作
if(get(a) == get(b)) return;
//将a节点所有合并的值累计给b节点
cnt[get(b)] += cnt[get(a)];
fa[get(a)] = get(b);
}
947. 移除最多的同行或同列石头
n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。
如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。
给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。
示例 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] 是平面上唯一一块石头,所以不可以移除它。
解题思路:我们可以将同行或者同列的坐标都利用并查集放到一个数组里(同根节点)根目录下,把可以连接的节点放在一起低效的话,就剩下一个节点了,此时fa父节点的数量就是剩下不能抵消的石头,所有最终的移除个数就是总石头数量减去剩下不能移除的数量,代码如下:
var removeStones = function(stones) {
fa = [];
unionSet(stones.length);
//记录x y轴有那些坐标
let px = {},py = {};
for(let i = 0; i < stones.length; i ++){
let x = stones[i][0];
let y = stones[i][1];
//判断当前想轴坐标是否存在,如果存在说明可以抵消,就合并
if(px[x] >= 0) merge(i,px[x]);
//同上
if(py[y] >= 0) merge(i,py[y]);
//记录坐标值
px[x] = i;
py[y] = i;
}
let cnt = 0;
//判断有几个父节点,存在的父节点就是剩下的节点个数
for(let i = 0; i < stones.length; i ++){
if(get(i) == i) cnt ++;
}
//抵消的节点等于总结点减剩余的节点
return stones.length - cnt;
};
//定义个保存父节点索引的数组,
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){
if(get(a) == get(b)) return;
fa[get(a)] = get(b);
}
1202. 交换字符串中的元素
给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。
你可以 任意多次交换 在 pairs 中任意一对索引处的字符。
返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。
示例 1:
输入:s = "dcab", pairs = [[0,3],[1,2]]
输出:"bacd"
解释:
交换 s[0] 和 s[3], s = "bcad"
交换 s[1] 和 s[2], s = "bacd"
示例 2:
输入:s = "dcab", pairs = [[0,3],[1,2],[0,2]]
输出:"abcd"
解释:
交换 s[0] 和 s[3], s = "bcad"
交换 s[0] 和 s[2], s = "acbd"
交换 s[1] 和 s[2], s = "abcd"
示例 3:
输入:s = "cba", pairs = [[0,1],[1,2]]
输出:"abc"
解释:
交换 s[0] 和 s[1], s = "bca"
交换 s[1] 和 s[2], s = "bac"
交换 s[0] 和 s[1], s = "abc"
解题思路:这题的解题思路是现将能交换的索引下标放到一个集合中,然后将这个能交换的集合按照字母从小到大排序,然后一次遍历出俩就是本体结果,这题的排序可以用到前面学习到的小顶堆来实现,也可以用排序但是很慢,还容易超时代码如下:
var smallestStringWithSwaps = function(s, pairs) {
//给并查集重新定义一个变量
let n = nuionFind;
//给堆重新定义一个变量名
let h = heap;
// 默认父组件的值
n.nuion(s.length);
for(let i = 0; i < pairs.length; i ++){
// 能交换的节点1
let a = pairs[i][0];
//能交换的节点2
let b = pairs[i][1];
//将能交换的两个节点合并
n.merge(a,b);
}
//将每个数组里的相对于字母放到daArr数组里
let faArr = [];
for(let i = 0; i < s.length; i ++){
//n.get(i) 获取到的是根节点,将根节点下的所有索引对应的字母找到保存到faArr里
let j = n.get(i)
faArr[j] = faArr[j] ? faArr[j] : [];
//将同一个数组里的字母按照小顶堆排序
h.push_up(s[i],faArr[j],'min');
}
let res = '';
for(let i = 0; i < s.length; i ++){
let j = n.get(i)
//因为是小顶堆,所有第一个就是最小的字母
res += faArr[j][0];
//删去第一个节点,并维持小顶堆特性
h.pop_down(faArr[j],'min')
}
return res;
};
let nuionFind = {
fa:[],
nuion(n){
for(let i = 0; i < n; i ++){
this.fa[i] = i;
}
},
get(x){
return this.fa[x] = this.fa[x] == x ? x : this.get(this.fa[x]);
},
merge(a,b){
this.fa[this.get(a)] = this.get(b)
}
}
//type = max 就是大顶堆,min就是小顶堆
let heap = {
push_up(val,data,type){
data.push(val);
let idx = data.length - 1;//当前节点
let gIdx = parseInt((idx - 1) / 2);//根节点
while(idx && this.CMP(idx,gIdx,data,type)){
data = this.swap(data[idx],idx,data[gIdx],gIdx,data);
idx = gIdx;
gIdx = parseInt((idx - 1)/2);
}
return data;
},
pop_down(data,type){
if(data.length <= 1){
data = [];
return data;
}
let cnt = data.length;
//删除最大值,将尾结点放在开头,位置树结构
data[0] = data.pop();
cnt --;
let idx = 0;//当前节点
let n = cnt -1;//最大节点个数
let zIdx = 2 * idx + 1;//左子节点
while(zIdx <= n){
let temp = idx;//三角区最大值下标
if(this.CMP(zIdx,idx,data,type)) temp = zIdx;
if(zIdx + 1 <= n && this.CMP(zIdx + 1,temp,data,type)) temp = zIdx + 1;
if(temp == idx) break;
data = this.swap(data[idx],idx,data[temp],temp,data);
idx = temp;
zIdx = 2*idx +1;
}
return data;
},
swap(v1,i1,v2,i2,data){
data[i1] = v2;
data[i2] = v1;
return data;
},
CMP(val,val2,data,type){
if(type == 'max'){
if(data[val] > data[val2]) return true;
else return false
}else{
if(data[val] < data[val2]) return true;
else return false
}
}
}