Given a string s
and an array of pairs pairs
where each pair [a, b]
indicates that you can swap the characters at indices a
and b
in s
as many times as you like, return the lexicographically smallest string that can be obtained after any number of swaps.
Constraints:
1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= a, b < s.length
s
consists of lowercase English letters.When first approaching this problem, you might think about simulating all possible swaps, trying every combination until the smallest string is found. However, this quickly becomes infeasible as the number of swaps and string length grows.
Instead, notice that the swap pairs define groups of indices that can be freely swapped among themselves. If two indices are connected (directly or indirectly) via the swap pairs, their characters can be rearranged in any order within that group.
The key insight is to identify these groups (connected components), and for each group, sort the characters and assign them to the sorted indices to get the smallest possible string.
This reduces the problem from simulating swaps to a problem about finding connected components and sorting.
To solve the problem efficiently, follow these steps:
pairs
is an undirected edge between two nodes.s
.We use Union-Find because it efficiently merges sets and finds the root of each component in nearly constant time, which is essential given the large constraints.
Example Input:
s = "dcab"
pairs = [[0,3],[1,2]]
"bacd"
This is the lexicographically smallest string obtainable with the allowed swaps.
s
, P = number of pairs, and α is the inverse Ackermann function (almost constant).The problem is elegantly solved by recognizing that swap pairs define groups of indices whose characters can be freely permuted. By using Union-Find to efficiently group these indices, and then sorting and reassigning their characters, we can construct the smallest possible string in optimal time. This approach leverages classic graph and sorting techniques, making the solution both efficient and conceptually clean.
class Solution:
def smallestStringWithSwaps(self, s, pairs):
parent = [i for i in range(len(s))]
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
parent[find(x)] = find(y)
for a, b in pairs:
union(a, b)
from collections import defaultdict
groups = defaultdict(list)
for i in range(len(s)):
root = find(i)
groups[root].append(i)
res = list(s)
for indices in groups.values():
chars = [s[i] for i in indices]
indices.sort()
chars.sort()
for idx, ch in zip(indices, chars):
res[idx] = ch
return ''.join(res)
class Solution {
public:
string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
int n = s.size();
vector<int> parent(n);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
};
for (auto& p : pairs) {
int a = p[0], b = p[1];
parent[find(a)] = find(b);
}
unordered_map<int, vector<int>> groups;
for (int i = 0; i < n; ++i)
groups[find(i)].push_back(i);
string res = s;
for (auto& kv : groups) {
vector<int>& indices = kv.second;
vector<char> chars;
for (int idx : indices) chars.push_back(s[idx]);
sort(indices.begin(), indices.end());
sort(chars.begin(), chars.end());
for (int i = 0; i < indices.size(); ++i)
res[indices[i]] = chars[i];
}
return res;
}
};
class Solution {
public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
int n = s.length();
int[] parent = new int[n];
for (int i = 0; i < n; ++i) parent[i] = i;
// Find with path compression
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
// Union
void union(int x, int y) {
parent[find(x)] = find(y);
}
for (List<Integer> p : pairs) {
union(p.get(0), p.get(1));
}
Map<Integer, List<Integer>> groups = new HashMap<>();
for (int i = 0; i < n; ++i) {
int root = find(i);
groups.computeIfAbsent(root, k -> new ArrayList<>()).add(i);
}
char[] res = s.toCharArray();
for (List<Integer> indices : groups.values()) {
List<Character> chars = new ArrayList<>();
for (int idx : indices) chars.add(s.charAt(idx));
Collections.sort(indices);
Collections.sort(chars);
for (int i = 0; i < indices.size(); ++i)
res[indices.get(i)] = chars.get(i);
}
return new String(res);
}
}
var smallestStringWithSwaps = function(s, pairs) {
const n = s.length;
const parent = Array.from({length: n}, (_, i) => i);
function find(x) {
if (parent[x] !== x) parent[x] = find(parent[x]);
return parent[x];
}
function union(x, y) {
parent[find(x)] = find(y);
}
for (const [a, b] of pairs) {
union(a, b);
}
const groups = {};
for (let i = 0; i < n; ++i) {
const root = find(i);
if (!(root in groups)) groups[root] = [];
groups[root].push(i);
}
const res = s.split('');
for (const indices of Object.values(groups)) {
const chars = indices.map(i => s[i]);
indices.sort((a, b) => a - b);
chars.sort();
for (let i = 0; i < indices.length; ++i) {
res[indices[i]] = chars[i];
}
}
return res.join('');
};