Smallest String With Swaps solution and explanation
9 more videos on this problem
Smallest String With Swaps | Leetcode 1202 | Union Find Application | Live coding sessionCoding Decoded · 11:35 · 7K views
1202 Smallest String With SwapsKelvin Chandra · 4:08 · 6K views
Leetcode - Smallest String With Swaps (Python)Timothy H Chang · 12:51 · 4K views
1202. Smallest String With SwapsTech Adora by Nivedita · 20:47 · 3K views
Leetcode 1202 - Smallest String (Union Find)The Coachable Dev · 13:42 · 2K views
1202. Smallest String With Swaps | Leetcode Daily Challenge | Using DFSVishal Rajput · 8:48 · 2K views
Daily Leetcode #757: Apr 27, 2022 - 1202. Smallest String With SwapsProgramming Live with Larry · 19:15 · 1K views
1202. Smallest String With SwapsTaroCode · 11:31 · 1K views
Smallest String With Swaps | Python Union FindEmily Bao · 15:05 · 811 views
1202. Smallest String With Swaps 交换字符串中的元素【LeetCode 力扣官方题解】LeetCode 力扣 · 12:40 · 585 views
The problem
You are given a string s, and an array of pairs of indices in the string pairs where pairs[i] = [a, b] indicates 2 indices(0-indexed) of the string.
You can swap the characters at any pair of indices in the given pairs any number of times.
Return the lexicographically smallest string that s can be changed to after using the swaps.
Example 1:
Input: s = "dcab", pairs = [[0,3],[1,2]] Output: "bacd" Explaination: Swap s[0] and s[3], s = "bcad" Swap s[1] and s[2], s = "bacd"
Example 2:
Input: s = "dcab", pairs = [[0,3],[1,2],[0,2]] Output: "abcd" Explaination: Swap s[0] and s[3], s = "bcad" Swap s[0] and s[2], s = "acbd" Swap s[1] and s[2], s = "abcd"
Example 3:
Input: s = "cba", pairs = [[0,1],[1,2]] Output: "abc" Explaination: Swap s[0] and s[1], s = "bca" Swap s[1] and s[2], s = "bac" Swap s[0] and s[1], s = "abc"
Constraints:
1 <= s.length <= 10^50 <= pairs.length <= 10^50 <= pairs[i][0], pairs[i][1] < s.lengthsonly contains lower case English letters.
Approach overview
Problem Overview: You receive a string s and a list of index pairs that can be swapped any number of times. If two indices are connected directly or indirectly through swaps, their characters can be rearranged freely. The goal is to produce the lexicographically smallest string possible.
Approach 1: Union-Find (Disjoint Set Union) (O(n log n) time, O(n) space)
This approach groups indices that can reach each other through swaps. Use a Union Find structure to connect indices from each pair. After processing all pairs, every index belongs to a component representing positions that can be freely reordered. Iterate through the string and collect characters belonging to each component. Sort the characters inside each group, then rebuild the result by assigning the smallest available character to each index in that component. The union operations run nearly in constant time with path compression, but sorting characters inside components results in O(n log n) total time.
Approach 2: DFS for Connected Components (O(n log n) time, O(n) space)
Instead of Union-Find, treat the problem as a graph. Each index is a node, and each swap pair creates an undirected edge. Build an adjacency list and run Depth-First Search to discover connected components. For every unvisited index, start DFS and collect all reachable indices. Extract the corresponding characters from the string, sort them, and place them back in sorted order at the sorted list of indices. The DFS traversal takes O(n + p) where p is the number of pairs, while sorting characters within components dominates the runtime, giving overall O(n log n).
The key insight in both approaches is that swaps create connectivity groups. Within a group, positions can be rearranged arbitrarily. Once components are identified, the optimal strategy is greedy: place the smallest available character at the smallest index to minimize the final lexicographic order.
Recommended for interviews: Union-Find is usually the expected solution because it handles connectivity cleanly and scales well when the number of pairs is large. DFS shows the same understanding using graph traversal and is equally valid. Demonstrating the brute idea—grouping indices by connectivity—shows problem understanding, while implementing Union-Find efficiently demonstrates stronger algorithmic skill.
Approach 1: Union-Find (Disjoint Set Union)
In this approach, we use the Union-Find (also known as Disjoint Set Union, DSU) data structure. This structure helps efficiently group indices that can be swapped. Once we group the indices, we can individually sort characters in each connected component to ensure the smallest lexicographical order.
Steps:
- Initialize a Union-Find structure for the indices of the string.
- Iterate through each pair in the 'pairs' array and perform union operations on the indices.
- Create a mapping from root parent to all indices in that connected component by leveraging the 'find' operation.
- For each component of indices, gather and sort the characters, then place them back in the original string positions according to indices mapping.
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.parent[rootY] = rootX
def smallestStringWithSwaps(s, pairs):
if not s:
return ""
n = len(s)
uf = UnionFind(n)
for x, y in pairs:
uf.union(x, y)
import collections
groups = collections.defaultdict(list)
for i in range(n):
root = uf.find(i)
groups[root].append(i)
res = list(s)
for indices in groups.values():
sorted_chars = sorted(res[i] for i in indices)
for i, char in zip(sorted(indices), sorted_chars):
res[i] = char
return ''.join(res)#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
class UnionFind {
public:
std::vector<int> parent;
UnionFind(int size) : parent(size) {
for (int i = 0; i < size; ++i) parent[i] = i;
}
int find(int x) {
if (x != parent[x]) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void union_sets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootY] = rootX;
}
}
};
std::string smallestStringWithSwaps(std::string s, std::vector<std::vector<int>>& pairs) {
int n = s.size();
UnionFind uf(n);
for (auto& p : pairs) {
uf.union_sets(p[0], p[1]);
}
std::unordered_map<int, std::vector<int>> groups;
for (int i = 0; i < n; ++i) {
groups[uf.find(i)].emplace_back(i);
}
for (auto& [_, indices] : groups) {
std::string t = "";
for (int index : indices) {
t += s[index];
}
std::sort(t.begin(), t.end());
for (size_t i = 0; i < indices.size(); ++i) {
s[indices[i]] = t[i];
}
}
return s;
}The provided Python code defines a UnionFind class to help manage connected components through union and find operations. The 'smallestStringWithSwaps' function makes use of this class to find connected components from the given pairs. For each component, it sorts the characters and places them back in the string res according to their indices.
Time Complexity: O(M * α(N) + NlogN), where M is the number of pairs, α is the inverse Ackermann function (very small constant), and N is the length of the string.
Space Complexity: O(N) for the union-find structure and the character groups.
Approach 2: DFS for Connected Components
This approach utilizes Depth-First Search (DFS) to find connected components in a graph that represents swap pairs. Each unique character component is individually sorted to form the smallest possible string.
Steps:
- Build an adjacency list for the graph using the pairs provided.
- For each index not yet visited, perform a DFS to discover all connected components.
- Gather all indices in a connected component, sort the characters corresponding to these indices, and re-position them in the string.
import java.util.*;
class Solution {
public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
int n = s.length();
List<Integer>[] graph = new List[n];
for (int i = 0; i < n; i++) {
graph[i] = new ArrayList<>();
}
for (List<Integer> pair : pairs) {
graph[pair.get(0)].add(pair.get(1));
graph[pair.get(1)].add(pair.get(0));
}
boolean[] visited = new boolean[n];
char[] res = s.toCharArray();
for (int i = 0; i < n; i++) {
if (!visited[i]) {
List<Integer> indices = new ArrayList<>();
List<Character> chars = new ArrayList<>();
dfs(i, graph, visited, indices, chars, res);
Collections.sort(indices);
Collections.sort(chars);
for (int j = 0; j < indices.size(); j++) {
res[indices.get(j)] = chars.get(j);
}
}
}
return new String(res);
}
private void dfs(int node, List<Integer>[] graph, boolean[] visited, List<Integer> indices, List<Character> chars, char[] res) {
visited[node] = true;
indices.add(node);
chars.add(res[node]);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(neighbor, graph, visited, indices, chars, res);
}
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
public class Solution {
public string SmallestStringWithSwaps(string s, IList<IList<int>> pairs) {
int n = s.Length;
List<int>[] graph = new List<int>[n];
for (int i = 0; i < n; i++) {
graph[i] = new List<int>();
}
foreach (var pair in pairs) {
graph[pair[0]].Add(pair[1]);
graph[pair[1]].Add(pair[0]);
}
bool[] visited = new bool[n];
char[] result = s.ToCharArray();
for (int i = 0; i < n; i++) {
if (!visited[i]) {
var indices = new List<int>();
var chars = new List<char>();
DFS(i, graph, visited, indices, chars, result);
indices.Sort();
chars.Sort();
for (int j = 0; j < indices.Count; j++) {
result[indices[j]] = chars[j];
}
}
}
return new string(result);
}
private void DFS(int node, List<int>[] graph, bool[] visited, List<int> indices, List<char> chars, char[] result) {
visited[node] = true;
indices.Add(node);
chars.Add(result[node]);
foreach (int neighbor in graph[node]) {
if (!visited[neighbor]) {
DFS(neighbor, graph, visited, indices, chars, result);
}
}
}
}The Java solution uses an adjacency list to represent the swap pairs as a graph. It performs a DFS from each unvisited node to discover all nodes connected to it, gathering indices and corresponding characters. These gathered components are sorted to ensure lexicographically smallest entries at their respective indices.
Time Complexity: O(N + M + C log C), where N is the number of nodes, M is the number of edges in the graph (pairs), and C is the size of the largest component.
Space Complexity: O(N + M), as we store the adjacency graph and visit flags.
Approach 3: Union-Find
We notice that the index pairs have transitivity, i.e., if a and b can be swapped, and b and c can be swapped, then a and c can also be swapped. Therefore, we can consider using a union-find data structure to maintain the connectivity of these index pairs, and sort the characters belonging to the same connected component in lexicographical order.
Finally, we traverse the string. For the character at the current position, we replace it with the smallest character in the connected component, then remove this character from the connected component, and continue to traverse the string.
The time complexity is O(n times log n + m times \alpha(m)), and the space complexity is O(n). Here, n and m are the length of the string and the number of index pairs, respectively, and \alpha is the inverse Ackermann function.
function smallestStringWithSwaps(s: string, pairs: number[][]): string {
const n = s.length;
const p = new Array(n).fill(0).map((_, i) => i);
const find = (x: number): number => {
if (p[x] !== x) {
p[x] = find(p[x]);
}
return p[x];
};
const d: string[][] = new Array(n).fill(0).map(() => []);
for (const [a, b] of pairs) {
p[find(a)] = find(b);
}
for (let i = 0; i < n; ++i) {
d[find(i)].push(s[i]);
}
for (const e of d) {
e.sort((a, b) => b.charCodeAt(0) - a.charCodeAt(0));
}
const ans: string[] = [];
for (let i = 0; i < n; ++i) {
ans.push(d[find(i)].pop()!);
}
return ans.join('');
}func smallestStringWithSwaps(s string, pairs [][]int) string {
n := len(s)
p := make([]int, n)
d := make([][]byte, n)
for i := range p {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
for _, pair := range pairs {
a, b := pair[0], pair[1]
p[find(a)] = find(b)
}
cs := []byte(s)
for i, c := range cs {
j := find(i)
d[j] = append(d[j], c)
}
for i := range d {
sort.Slice(d[i], func(a, b int) bool { return d[i][a] > d[i][b] })
}
for i := range cs {
j := find(i)
cs[i] = d[j][len(d[j])-1]
d[j] = d[j][:len(d[j])-1]
}
return string(cs)
}impl Solution {
#[allow(dead_code)]
pub fn smallest_string_with_swaps(s: String, pairs: Vec<Vec<i32>>) -> String {
let n = s.as_bytes().len();
let s = s.as_bytes();
let mut disjoint_set: Vec<usize> = vec![0; n];
let mut str_vec: Vec<Vec<u8>> = vec![Vec::new(); n];
let mut ret_str = String::new();
// Initialize the disjoint set
for i in 0..n {
disjoint_set[i] = i;
}
// Union the pairs
for pair in pairs {
Self::union(pair[0] as usize, pair[1] as usize, &mut disjoint_set);
}
// Initialize the return vector
for (i, c) in s.iter().enumerate() {
let p_c = Self::find(i, &mut disjoint_set);
str_vec[p_c].push(*c);
}
// Sort the return vector in reverse order
for cur_vec in &mut str_vec {
cur_vec.sort();
cur_vec.reverse();
}
// Construct the return string
for i in 0..n {
let index = Self::find(i, &mut disjoint_set);
ret_str.push(str_vec[index].last().unwrap().clone() as char);
str_vec[index].pop();
}
ret_str
}
#[allow(dead_code)]
fn find(x: usize, d_set: &mut Vec<usize>) -> usize {
if d_set[x] != x {
d_set[x] = Self::find(d_set[x], d_set);
}
d_set[x]
}
#[allow(dead_code)]
fn union(x: usize, y: usize, d_set: &mut Vec<usize>) {
let p_x = Self::find(x, d_set);
let p_y = Self::find(y, d_set);
d_set[p_x] = p_y;
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Union-Find (Disjoint Set Union) | Time Complexity: O(M * α(N) + NlogN), where M is the number of pairs, α is the inverse Ackermann function (very small constant), and N is the length of the string. Space Complexity: O(N) for the union-find structure and the character groups. |
| DFS for Connected Components | Time Complexity: O(N + M + C log C), where N is the number of nodes, M is the number of edges in the graph (pairs), and C is the size of the largest component. Space Complexity: O(N + M), as we store the adjacency graph and visit flags. |
| Union-Find | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Union-Find (Disjoint Set Union) | O(n log n) | O(n) | Best general solution when many swap pairs exist and connectivity needs to be merged efficiently |
| DFS Connected Components | O(n log n) | O(n) | Useful when modeling swaps as a graph and performing traversal to collect components |
Frequently asked questions
Is Smallest String With Swaps easy or hard?
Smallest String With Swaps is considered a medium-level problem. The main challenge is recognizing that swap pairs form connected components. Once the problem is reduced to grouping indices and sorting characters inside each group, the implementation becomes straightforward.
Smallest String With Swaps Python/Java solution
In Python or Java, implement Union-Find to connect indices from each pair, then map root parents to their indices. Extract characters from those indices, sort them, and rebuild the string by placing the smallest characters first. The same logic works across Python, Java, C++, and C#.
How to solve Smallest String With Swaps in O(n)?
A strict O(n) solution is not practical because characters inside each connected component must be ordered to produce the smallest lexicographic result. Sorting those characters introduces an O(n log n) factor. Union-Find or DFS plus sorting is the standard optimal solution.
What is the best approach for Smallest String With Swaps?
The most common approach is Union-Find (Disjoint Set Union). It groups indices connected by swap pairs into components. Characters within each component are sorted and placed back at their indices to produce the smallest lexicographic string. The overall complexity is O(n log n) due to sorting.
Is Smallest String With Swaps asked at Google/Amazon/Meta?
Smallest String With Swaps appears in interview preparation sets for companies like Google, Amazon, and Meta because it combines graph connectivity with string manipulation. Interviewers often expect candidates to recognize the Union-Find pattern for grouping swappable indices.
What data structure is used in Smallest String With Swaps?
The main data structures are Union-Find (Disjoint Set Union) or a graph adjacency list for DFS/BFS traversal. Hash maps or dictionaries are also used to group indices and characters belonging to the same connected component before sorting them.
What is the time complexity of Smallest String With Swaps?
The optimal solution runs in O(n log n) time. Building connected components using Union-Find or DFS takes roughly O(n + p), where p is the number of pairs. Sorting characters inside each component dominates the runtime, leading to O(n log n) overall.
