问题描述
给你一个字符串 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”
分析
审题可以发现,pairs是给定的成对的可交换的位置。另外,如果两对可交换的集合有交集的话,例如[1, 2]和[2, 3],那么下标为1、2、3的三个数可以交换成任意顺序,自然就可以交换得到这三个字符能够组成的字典序最小的字符串。那么多几对都有交集的情况呢?通过观察也是这样的,所以我们可以通过并查集的方法,来将这些索引对看成是集合,有交集的可以联通在一起,通过并查集将它们有交集的归为一个集合。假设得到三个集合,我们只需要分别对三个集合排序(使用优先队列)就可以了,最后从下标0开始遍历,每次取该下标所属集合的最小的那个字符即可,选取后同时将其从该集合的优先队列中弹出。
class Solution {
// 并查集
int[] pre;
public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
if (pairs.size() == 0)
return s;
int len = s.length();
pre = new int[len];
for (int i = 0; i < len; i++) {
pre[i] = i;
}
// 使用并查集进行划分
for (int i = 0; i < pairs.size(); i++) {
union(pairs.get(i).get(0), pairs.get(i).get(1));
}
// 并查集中每个集合都有个root,将该集合中的字符通过优先队列进行排序
Map<Integer, PriorityQueue<Character>> map = new HashMap<>();
for (int i = 0; i < len; i++) {
int root = find(i);
if (map.containsKey(root)) {
map.get(root).offer(s.charAt(i));
} else {
PriorityQueue<Character> heap = new PriorityQueue<>();
heap.offer(s.charAt(i));
map.put(root, heap);
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
int root = find(i);
sb.append(map.get(root).poll());
}
return sb.toString();
}
public int find(int root) {
if (pre[root] == root) {
return root;
}
return pre[root] = find(pre[root]);
}
public void union(int a, int b) {
int x = find(a);
int y = find(b);
if (x != y) {
pre[x] = y;
}
}
}