「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」
题目
给你一个字符串 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 中只含有小写英文字母
来源:力扣(LeetCode)leetcode-cn.com/problems/sm…
解题思路
可以交换的字符下标是一种连通关系,连通性关系都可以用并查集来解决。
处理过程:
- 初始化并查集,节点个数是字符串长度;
- 把能够交换的位置合并到一个集合当中;
- 通过小顶堆将同一个集合的字符排序;
- 最后循环获取小顶堆的顶部元素,合并成结果;
代码实现
var smallestStringWithSwaps = function (s, pairs) {
const len = s.length
//初始化并查集,节点个数是字符串长度
const u = new UnionSet(len)
//把能够交换的位置合并到一个集合当中
for (const [a, b] of pairs) {
u.merge(a, b)
}
//初始化 len 个最小优先队列,将同一个集合的字符排序
const h = new Array(len).fill(0).map(() => new MinPriorityQueue())
for (let i = 0; i < len; i++) {
h[u.get(i)].enqueue(s[i], s[i].charCodeAt())
}
//合并结果
let ans = ''
for (let i = 0; i < len; i++) {
//根据字符下标获取小顶堆,取堆顶字符拼接结果
ans += h[u.get(i)].front()['element']
h[u.get(i)].dequeue()
}
return ans
};
class UnionSet {
constructor(n) {
//初始化父节点数组,每个节点的父节点默认为自己
this.pa = new Array(n + 1).fill(0).map((item, index) => index)
//初始化每棵树的节点数
this.size = new Array(n + 1).fill(1)
}
get(x) {
//查找x的父节点,并且完成路径优化
//优化后,x的父节点指向所在树的根节点
return this.pa[x] = this.pa[x] === x ? x : this.get(this.pa[x])
}
merge(a, b) {
//找到a的根节点
const ra = this.get(a)
//找到b的根节点
const rb = this.get(b)
//如果a和b在一个集合中则不需要合并
if (ra === rb) return
//把节点总数小的集合合并到节点总数多的集合里
//更新节点总数多的集合为 a和b之和
if (this.size[ra] < this.size[rb]) {
this.pa[ra] = rb
this.size[rb] += this.size[ra]
} else {
this.pa[rb] = ra
this.size[ra] += this.size[rb]
}
}
}
如有错误欢迎指出,欢迎一起讨论!