[路飞]_交换字符串中的元素

329 阅读1分钟

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战

leetcode-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 中只含有小写英文字母

解题思路

我们将题目中的例子做成动画来看一下元素之间交换的过程

演示文稿 4(4).gif

  1. 在这个例子中,首先我们将能够交换的元素下标进行连通
  2. 而两个可交换元素之间的交换次数和顺序是没有限制的,这就意味着元素连通之后在一个集合中的元素可以经过若干次的两两交换,最后将集合中的元素进行从小到大的排序

那么我们将所有的元素按连通关系分成不同的集合,并且将各个集合中的元素按从小到大排序之后,怎么将不同集合之间的元素按下标拼成一个新的字符串呢?

演示文稿 4(5).gif 如动画中的例子所示

  1. 首先将该字符串中的元素根据连通关系分成 3 个集合
  2. 每个集合用一个小顶堆来维护集合中的元素,小顶堆的编号就是集合中根节点的下标
  3. 遍历原来字符串的长度,当走到哪个下标时,就获取对应下标所在集合中根节点的下标所对应的小顶堆的堆顶元素(可能比较难理解,下面用例子说明)
  • 下标 0 的集合根元素下标是 0,获取 0 号小顶堆的根节点 astr = 'a'
  • 下标 1 的集合根元素下标是 1,获取 1 号小顶堆的根节点 bstr = 'ab'
  • 下标 2 的集合根元素下标是 1,获取 1 号小顶堆的根节点 cstr = 'abc'
  • 下标 3 的集合根元素下标是 3,获取 3 号小顶堆的根节点 dstr = 'abcd'
  • 下标 4 的集合根元素下标是 1,获取 1 号小顶堆的根节点 estr = 'abcde'
  • 下标 5 的集合根元素下标是 3,获取 3 号小顶堆的根节点 gstr = 'abcdeg'
  • 下标 6 的集合根元素下标是 3,获取 3 号小顶堆的根节点 rstr = 'abcdegr'
  • 下标 7 的集合根元素下标是 1,获取 1 号小顶堆的根节点 fstr = 'abcdegrf'

解题代码

var smallestStringWithSwaps = function(s, pairs) {
    // 将元素之间通过连通关系分成不同的集合
    const unionSet = new UnionSet(s.length)
    for (const pair of pairs) {
        unionSet.merge(pair[0], pair[1])
    }

    // 维护每个集合的小顶堆,小顶堆的编号为当前元素所在集合的根节点元素的下标
    const map = new Map()
    for (let i = 0; i < s.length; i++) {
        const root = unionSet.get(i)
        if (!map.has(root)) map.set(root, new Heap())
        map.get(root).push(s[i])
    }

    // 从遍历到的下标所在的小顶堆中弹出堆顶元素进行拼接
    let str = ''
    for (let i = 0; i< s.length; i++) {
        const root = unionSet.get(i)
        str += map.get(root).pop()
    }

    return str
};

// 并查集
class UnionSet {
    constructor(n) {
        this.fa = []
        this.size = []
        for(let i = 0; i < n; i++) {
            this.fa[i] = i
            this.size[i] = 1
        }
    }

    get(v) {
        if(this.fa[v] === v) return v
        const root = this.get(this.fa[v])
        this.fa[v] = root
        return root
    }

    merge(a, b) {
        const ra = this.get(a), rb = this.get(b)
        if (ra === rb) return
        if (this.size[ra] < this.size[rb]) {
            this.fa[ra] = rb
            this.size[rb] += this.size[ra]
        } else {
            this.fa[rb] = ra
            this.size[ra] += this.size[rb]
        }
    }
}

// 小顶堆
class Heap {
    constructor() {
        this.arr = []
    }

    push(val) {
        this.arr.push(val)
        this._sortBack()
    }

    pop() {
        const val = this.arr[0]
        const back = this.arr.pop()
        if (this.arr.length) {
            this.arr[0] = back
            this._sortFront()
        }
        return val
    }

    _sortBack() {
        let i = this.arr.length - 1
        while (i > 0 && this.arr[i] < this.arr[Math.floor((i - 1) / 2)]) {
            [this.arr[i], this.arr[Math.floor((i - 1) / 2)]] = [this.arr[Math.floor((i - 1) / 2)], this.arr[i]]
            i = Math.floor((i - 1) / 2)
        }
    }

    _sortFront() {
        let i = 0
        while (2 * i + 1 < this.arr.length) {
            let temp = i
            if (this.arr[temp] > this.arr[2 * i + 1]) temp = 2 * i + 1
            if (2 * i + 2 < this.arr.length && this.arr[temp] > this.arr[2 * i + 2]) temp = 2 * i + 2
            if (temp === i) break
            [this.arr[temp], this.arr[i]] = [this.arr[i], this.arr[temp]]
            i = temp
        }
    }
}