[前端]_一起刷leetcode 1202. 交换字符串中的元素

539 阅读3分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

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"

 

提示:

  • 1 <= s.length <= 10^5
  • 0 <= pairs.length <= 10^5
  • 0 <= pairs[i][0], pairs[i][1] < s.length
  • s 中只含有小写英文字母

思路

这道题目也是一道比较典型的并查集的题目,我们可以分几个步骤来处理。

  1. 遍历联系数组,把关联关系建议起来;
  2. 遍历字符串,把同一个关联关系下的数据归类到一个数组中,用map记录;
  3. 遍历map, 对各个关联关系的数组做排序;
  4. 遍历初始化节点,从map中一一取出构造结果。

翻车现场

/**
 * @param {string} s
 * @param {number[][]} pairs
 * @return {string}
 */
var smallestStringWithSwaps = function(s, pairs) {
    const n = s.length;
    const uf = new UnionFind(n);

    // 先把所有的关联项连接起来
    for (let i = 0; i < pairs.length; i++) {
        const [ a, b ] = pairs[i];
        uf.merge(a, b);
    }

    // 枚举所有的集合
    let map = new Map();
    for (let i = 0; i < n; i++) {
        const index = uf.find(i);
        let cur = map.get(index) || [];
        map.set(index, [...cur, s[i]]);
    }

    // 对枚举值进行排序
    for (let [ key, value ] of map) {
        value.sort((a, b) => a.charCodeAt() - b.charCodeAt());
    }

    // 返回结果,每个节点的值累加
    let result = "";
    for (let i = 0; i < n; i++) {
        let cur = map.get(uf.parent[i]);
        result += cur.shift();
        map.set(uf.parent[i], cur);
    }

    return result;
};

class UnionFind {
  constructor(n) {
    // 一开始每个元素的父元素都是自己
    this.parent = new Array(n).fill(0).map((item, index) => index);
  }

  // 找到元素的父元素
  find(index) {
    return this.parent[index] = this.parent[index] === index ? index : this.find(this.parent[index]);
  }

  // 把index2的父元素设置为index1的父元素
  merge(index1, index2) {
    this.parent[this.find(index2)] = this.find(index1);
  }
}

image.png

虽然我们没有嵌套的结构,但是在map不断取值和赋值的这一步,确实很耗性能,我们想想办法跳过这一步,看能不能直接通过数组来排。想一下优化方法,让代码变得优雅。

优化

/**
 * @param {string} s
 * @param {number[][]} pairs
 * @return {string}
 */
var smallestStringWithSwaps = function(s, pairs) {
    const n = s.length;
    const uf = new UnionFind(n);

    // 先把所有的关联项连接起来
    for (let i = 0; i < pairs.length; i++) {
        const [ a, b ] = pairs[i];
        uf.merge(a, b);
    }

    // 枚举所有的集合
    let list = new Array(n).fill(0).map(v => new Array());
    for (let i = 0; i < n; i++) {
        const index = uf.find(i);
        list[index].push(s[i]);
    }

    
    // 对枚举值进行排序
    for (let i = 0; i < n; i++) {
        list[i].sort((a, b) => b.charCodeAt() - a.charCodeAt());
    }

    // 返回结果,每个节点的值累加
    let result = "";
    for (let i = 0; i < n; i++) {
        const index = uf.find(i);
        result += list[index].pop();
    }

    return result;
};

class UnionFind {
  constructor(n) {
    // 一开始每个元素的父元素都是自己
    this.parent = new Array(n).fill(0).map((item, index) => index);
  }

  // 找到元素的父元素
  find(index) {
    return this.parent[index] = this.parent[index] === index ? index : this.find(this.parent[index]);
  }

  // 把index2的父元素设置为index1的父元素
  merge(index1, index2) {
    this.parent[this.find(index2)] = this.find(index1);
  }
}

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。