Given a string s of length N and a list of allowed index swaps (pairs of indices where characters may be swapped any number of times), produce the lexicographically largest string obtainable by applying any sequence of the allowed swaps.
Intuition: allowed swaps define an undirected graph over indices; any indices in the same connected component can permute their characters arbitrarily. So for each connected component, collect its characters, sort them in descending order, and place the largest available characters into the component’s indices in ascending index order.
classSolution {
classDSU(n:Int){
val p = IntArray(n){it}
funfind(x:Int):Int = if(p[x]==x) x else { p[x]=find(p[x]); p[x] }
fununite(a:Int,b:Int){ val x=find(a); val y=find(b); if(x!=y) p[y]=x }
}
funlargestStringWithSwaps(s:String, swaps: List<IntArray>): String {
val n = s.length
val d = DSU(n)
for (p in swaps) d.unite(p[0], p[1])
val groups = mutableMapOf<Int, MutableList<Char>>()
for (i in0 until n) groups.getOrPut(d.find(i)) { mutableListOf() }.add(s[i])
for (lst in groups.values) lst.sort()
val sb = StringBuilder()
for (i in0 until n) sb.append(groups[d.find(i)]!!.removeAt(groups[d.find(i)]!!.size-1))
return sb.toString()
}
}
from collections import defaultdict
classDSU:
def__init__(self,n):
self.p=list(range(n))
deffind(self,a):
if self.p[a]!=a: self.p[a]=self.find(self.p[a])
return self.p[a]
defunite(self,a,b):
a=self.find(a); b=self.find(b)
if a!=b: self.p[b]=a
deflargestStringWithSwaps(s: str, swaps: list[tuple[int,int]]) -> str:
n=len(s)
d=DSU(n)
for a,b in swaps: d.unite(a,b)
groups=defaultdict(list)
for i,ch in enumerate(s): groups[d.find(i)].append(ch)
for k in groups: groups[k].sort()
res=[]
for i in range(n):
grp=groups[d.find(i)]
res.append(grp.pop())
return''.join(res)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::collections::BTreeMap;
structDSU{p:Vec<usize>}
implDSU{fnnew(n:usize)->Self{letmut p=vec![0;n]; for i in0..n{p[i]=i} Self{p}} fnfind(&mut self,x:usize)->usize{ if self.p[x]==x {x} else { let r=self.find(self.p[x]); self.p[x]=r; r } } fnunite(&mut self,a:usize,b:usize){ let x=self.find(a); let y=self.find(b); if x!=y { self.p[y]=x } }}
fnlargestStringWithSwaps(s: &str, swaps: &[(usize,usize)]) -> String {
let n = s.len();
letmut d =DSU::new(n);
for&(a,b) in swaps { d.unite(a,b); }
letmut groups: BTreeMap<usize, Vec<char>>= BTreeMap::new();
for (i,ch) in s.chars().enumerate() { groups.entry(d.find(i)).or_default().push(ch); }
for (_k,v) in groups.iter_mut() { v.sort(); }
letmut ans = String::with_capacity(n);
for i in0..n { let g = d.find(i); let v = groups.get_mut(&g).unwrap(); ans.push(v.pop().unwrap()); }
ans
}
Use a max-heap per component (priority queue ordered descending). Insert each character into its component heap, then reconstruct by polling the heap for each index. This avoids explicit per-component sort and is convenient when a language provides a PQ implementation.
A priority queue per component always yields the currently largest available character in that component. By inserting all characters of a component into a max-heap, polling the heap gives characters in descending order without a separate sorting step.
import java.util.PriorityQueue
classSolution {
classDSU(n:Int){ val p = IntArray(n){it}; funfind(x:Int):Int = if(p[x]==x) x else { p[x]=find(p[x]); p[x] }; fununite(a:Int,b:Int){ val x=find(a); val y=find(b); if(x!=y) p[y]=x } }
funlargestStringWithSwaps(s:String, swaps: List<IntArray>): String {
val n = s.length
if (n==0) return""val d = DSU(n)
for (p in swaps) d.unite(p[0], p[1])
val groups = mutableMapOf<Int, PriorityQueue<Char>>()
for (i in0 until n) {
val g = d.find(i)
groups.computeIfAbsent(g) { PriorityQueue<Char>(compareByDescending { it }) }.add(s[i])
}
val sb = StringBuilder()
for (i in0 until n) sb.append(groups[d.find(i)]!!.poll())
return sb.toString()
}
}
import heapq
from collections import defaultdict
classDSU:
def__init__(self,n): self.p=list(range(n))
deffind(self,a):
if self.p[a]!=a: self.p[a]=self.find(self.p[a])
return self.p[a]
defunite(self,a,b):
a=self.find(a); b=self.find(b)
if a!=b: self.p[b]=a
deflargestStringWithSwaps(s: str, swaps: list[tuple[int,int]]) -> str:
n = len(s)
if n ==0: return"" d = DSU(n)
for a,b in swaps: d.unite(a,b)
groups = defaultdict(list)
for i,ch in enumerate(s):
g = d.find(i)
# use min-heap of negative ord to simulate max-heap heapq.heappush(groups[g], (-ord(ch), ch))
res = []
for i in range(n): res.append(heapq.heappop(groups[d.find(i)])[1])
return''.join(res)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::collections::{HashMap, BinaryHeap};
structDSU{p:Vec<usize>}
implDSU{fnnew(n:usize)->Self{letmut p=vec![0;n]; for i in0..n{p[i]=i} Self{p}} fnfind(&mut self,x:usize)->usize{ if self.p[x]==x {x} else { let r=self.find(self.p[x]); self.p[x]=r; r } } fnunite(&mut self,a:usize,b:usize){ let x=self.find(a); let y=self.find(b); if x!=y { self.p[y]=x } }}
fnlargestStringWithSwaps(s: &str, swaps: &[(usize,usize)]) -> String {
let n = s.len(); if n==0 { return String::new(); }
letmut d =DSU::new(n);
for&(a,b) in swaps { d.unite(a,b); }
letmut groups: HashMap<usize, BinaryHeap<char>>= HashMap::new();
for (i,ch) in s.chars().enumerate() { groups.entry(d.find(i)).or_default().push(ch); }
letmut ans = String::with_capacity(n);
for i in0..n { let g = d.find(i); let heap = groups.get_mut(&g).unwrap(); ans.push(heap.pop().unwrap()); }
ans
}
⏰ Time complexity: O(N log K) where K is size of the largest component — inserting N characters into per-component heaps costs O(N log K) in total and each poll costs O(log K); overall dominated by heap operations and is comparable to sorting when summed across components.
🧺 Space complexity: O(N) to store characters in the heaps plus O(N) for DSU structures.