Smallest String With Swaps
You are given a string s, and an array of pairs of indices pairs where pairsi = [ai, bi] indicates that you may swap the characters at indices ai and bi in s. You can swap indices any number of times. Return the lexicographically smallest string that s can be changed to after using the swaps. Union every swappable pair of indices into groups . Inside each group, any arrangement of characters is reachable, so sort each group and place the smallest characters at the group's smallest indices.
Constraints
- 1 ≤ s.length ≤ 105
- 0 ≤ pairs.length ≤ 105
- 0 ≤ indices in pairs < s.length
- s consists of lowercase English letters
Example
s = "dcab", pairs = [[0,3],[1,2]]"bacd"Explanation Indices 0 and 3 form one group, indices 1 and 2 form another. Sorting each group's characters gives "bacd".
Union swappable indices, sort characters within each group
Union indices 0 and 3 from pair [0, 3].
What happens in this step
union(0, 3)
find(0) → 0 find(3) → 3
roots differ → union: parent[0] = 3
parent array: [0, 1, 2, 3] → [3, 1, 2, 3]
Indices 0 and 3 merge into one group, root 3 — their characters ('d' and 'b') can now be freely rearranged between them.Steps to visualize
- Union every pair of indices given in pairs.
- Group every index by its root — indices in the same group can trade characters freely.
- For each group, collect its characters, sort them, and hand the smallest ones to the smallest indices.
- Join the rewritten characters back into a string.
Walk through the code
Same walkthrough, now with the code. Press Next to move one step and watch which lines run.
Union indices 0 and 3 from pair [0, 3].
What happens in this step
union(0, 3)
find(0) → 0 find(3) → 3
roots differ → union: parent[0] = 3
parent array: [0, 1, 2, 3] → [3, 1, 2, 3]
Indices 0 and 3 merge into one group, root 3 — their characters ('d' and 'b') can now be freely rearranged between them.Solution
function smallestStringWithSwaps(s, pairs) {
const n = s.length;
const parent = Array.from({ length: n }, (_, i) => i);
function find(x) {
while (parent[x] !== x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
function union(a, b) {
const rootA = find(a);
const rootB = find(b);
if (rootA !== rootB) {
parent[rootA] = rootB;
}
}
for (const [a, b] of pairs) {
union(a, b);
}
const groups = new Map();
for (let i = 0; i < n; i++) {
const root = find(i);
if (!groups.has(root)) groups.set(root, []);
groups.get(root).push(i);
}
const result = s.split('');
for (const indices of groups.values()) {
const chars = indices.map((i) => s[i]).sort();
indices.sort((a, b) => a - b);
indices.forEach((index, position) => {
result[index] = chars[position];
});
}
return result.join('');
}- Time
- O(n log n)
- Space
- O(n)
Test cases
| Input | Expected | Covers |
|---|---|---|
s = "dcab", pairs = [[0,3],[1,2]] | "bacd" | example from the docstring |
s = "dcab", pairs = [[0,3],[1,2],[0,2]] | "abcd" | every index ends up in the same group, so the string becomes fully sorted |
s = "cba", pairs = [[0,1],[1,2]] | "abc" | pairs chain together transitively into one group |
s = "abc", pairs = [] | "abc" | no swaps available, the string is unchanged |
s = "ba", pairs = [[0,1]] | "ab" | smallest non-trivial case, a single swappable pair |
s = "dcab", pairs = [[0,3],[3,0]] | "bcad" | a duplicated pair only affects the indices it names, leaving the rest untouched |