描述
给你一个字符串 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)
链接:https://leetcode-cn.com/problems/smallest-string-with-swaps/
求解
// 辅助实现类,并查集
class UnionFind {
public:
UnionFind(int n) {
rank.reserve(n);
parent.reserve(n);
for (int i = 0; i < n; ++i) {
rank[i] = 1;
parent[i] = i;
}
count = n;
}
bool isConnected(int p, int q) {
assert(p < count);
assert(q < count);
return find(p) == find(q);
}
void unionElements(int p, int q) {
assert(p < count);
assert(q < count);
int pParent = find(p);
int qParent = find(q);
if (pParent == qParent) {
return;
}
if (rank[pParent] < rank[qParent]) {
parent[pParent] = qParent;
return;
}
if (rank[pParent] > rank[qParent]) {
parent[qParent] = pParent;
return;
}
// rank[pParent] == rank[qParent]
parent[pParent] = qParent;
++rank[qParent];
}
int find(int p) {
assert(p < count);
while (p != parent[p]) {
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
private:
vector<int> parent;
int count;
vector<int> rank;
};
class Solution {
private:
vector<bool> visited; // 标识点是否被访问
vector<int> id; // 每个点的连通分量
int count = 0; // 连通分量
void dfs(const vector<vector<int>> &edges, int v) {
visited[v] = true;
id[v] = count;
for (auto w : edges[v]) {
if (!visited[w]) {
dfs(edges, w);
}
}
}
public:
// 方法一,用并查集对每个连通分量内部字符进行排序
string smallestStringWithSwaps_useUnionFind(string s, vector<vector<int>> &pairs) {
const int n = s.size();
if (n < 2) {
return s;
}
// 利用所有可以交换的点信息构造并查集
UnionFind uf(n);
for (const auto &pair : pairs) {
uf.unionElements(pair[0], pair[1]);
}
// 计算并查集的连通分量
std::unordered_map<int, vector<int>> record; // 记录每一个连通分量内的所有位置字符
for (int i = 0; i < n; ++i) {
int ip = uf.find(i);
// if (record.count(ip) == 0) {
// record.emplace(ip, vector<int>{i});
// continue;
// }
// auto &vec = record[ip];
// vec.push_back(i);
// 啰嗦的代码优化
// 这儿不用去判断ip在散列表中是否存在了,value是一个vector,总之都是要构造一个vector的
record[ip].push_back(i);
}
// 在每一个联通分量内部对字符串进行排序
string res = s;
for (auto &[k, vec] : record) {
// 循环内部实现方法一
// 使用carr辅助空间存放每个连通分量的所有字符
/*vector<char> carr;
carr.reserve(vec.size());
for (int index : vec) {
carr.emplace_back(res[index]);
}
// 每个连通分量的位置、字符排序
std::sort(vec.begin(), vec.end()); // 每个连通分量内部的位置按照从小到大排序
std::sort(carr.begin(), carr.end()); // 每个连通分量内部字符按照字典序进行排序
for (int i = 0; i < vec.size(); ++i) {
// 将排序好的字符从新赋值回原来的连通分量位置(位置从小到大,字符按照字典序从小到大)
res[vec[i]] = carr[i];
}*/
// 循环内部实现方法二
// 使用一个数组辅助空间存放每个连通分量的原来位置,直接对位置进行排序
vector<int> cp = vec; // 位置留存一份副本
std::sort(vec.begin(), vec.end(), [&](int a, int b) { return s[a] < s[b]; });
for (int i = 0; i < vec.size(); ++i) {
res[cp[i]] = s[vec[i]]; // 理解
}
}
return res;
}
// 方法二,利用图的深度遍历得到连通分量
string smallestStringWithSwaps(string s, vector<vector<int>> &pairs) {
const int n = s.size();
if (n < 2) {
return s;
}
// 构造图
vector<vector<int>> edges; // 利用邻接表存储图
edges.resize(n, vector<int>());
for (auto &pair : pairs) {
edges[pair[0]].push_back(pair[1]);
edges[pair[1]].push_back(pair[0]);
}
// 初始化状态,深度遍历图
visited.assign(n, false);
id.assign(n, 0);
count = 0;
for (int i = 0; i < n; ++i) {
if (!visited[i]) {
++count;
dfs(edges, i);
}
}
// 图深度遍历完成,得到不同的连通分量,处理同方法一中后半的部分
std::unordered_map<int, vector<int>> record; // 记录每一个连通分量内的所有位置字符
for (int i = 0; i < n; ++i) {
// 仅处理连通分量大于0的位置,等于0的位置表征不进行任何交换
if (id[i] != 0) {
record[id[i]].push_back(i);
}
}
// 在每一个联通分量内部对字符串进行排序
string res = s;
for (auto &[k, vec] : record) {
// 使用一个数组辅助空间存放每个连通分量的原来位置,直接对位置进行排序
vector<int> cp = vec; // 位置留存一份副本
std::sort(vec.begin(), vec.end(), [&](int a, int b) { return s[a] < s[b]; });
for (int i = 0; i < vec.size(); ++i) {
res[cp[i]] = s[vec[i]]; // 理解
}
}
return res;
}
};