【路飞】128. 最长连续序列、947. 移除最多的同行或同列石头、1202. 交换字符串中的元素

113 阅读1分钟

并查集代码

并查集的思想是用一个数组表示了整片森林(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
        }
    }
}