给你一个字符串 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"
解题思路
题目给出可以使用「索引对」来任意交换字符串,所以我们可以看成在每个「索引对」中组成的集合所在的字符是可以任意交换的。
如:
例1中,[0, 3] [1, 2] 是两个不同的「索引对」他们组成的集合的下标是可以任意交换,也就是b和d、c和a可以任意的交换。
例2中,[0, 3] [1, 2] [0, 2] 每个「索引对」都相关所以这个三个「索引对」是一个集合 [0,1,2,3]的下标可以任意交换。
我们可以使用并查集来将「索引对」的每个下标联通,然后获取联通后的集合,将集合排序,最后整合成排序后的字符串。
代码
class UnionFind {
constructor(n) {
this.parent = new Array(n).fill(0).map((v, index)=>index)
this.rank = new Array(n).fill(1)
this.count = n
}
getCount() {
return this.count
}
find(x) {
if (this.parent[x] != x) {
this.parent[x] = this.find(this.parent[x])
}
return this.parent[x]
}
unit(a, b) {
let ra = this.find(a), rb = this.find(b)
if (ra == rb) return
if (this.rank[ra] < this.rank[rb]) {
this.parent[ra] = rb
this.rank[rb] += this.rank[ra]
} else {
this.parent[rb] = ra
this.rank[ra] += this.rank[rb]
}
this.count--
}
}
var smallestStringWithSwaps = function(s, pairs) {
let uf = new UnionFind(s.length)
// 1. 将可以进行交换的字符进行联通
for(pair of pairs) {
uf.unit(pair[0], pair[1])
}
// 2. 将联通的字符串存入到一个新的数组里面进行排序
let charCodeArr = new Array(s.length).fill(0).map(()=>new Array())
for(let i = 0; i < s.length; i++) {
charCodeArr[uf.find(i)].push(s[i])
}
// 3. 将每组字符串按照字典序列排序(转成ASCII)
for (let i = 0; i < s.length; i++) {
if (charCodeArr[i].length) {
charCodeArr[i].sort((a,b)=> b.charCodeAt() - a.charCodeAt())
}
}
// 4. 通过原始字符下标获取字符串的组号,拼接字符串
let ans = new Array(s.length)
for (let i = 0; i < s.length; i++) {
// 当前字符的组号
let index = uf.find(i)
// 当前字符数组
let str = charCodeArr[index]
// 获取当前字符数组的最后一个字符填充到返回数组中
ans[i] = str.pop()
}
return ans.join('')
};