- graph representation of the problem
- understanding the disjoint sets approach to the problem
π‘ Explanation of Solution
Visualizing the Problem as a Graph Problem
Nodes: each index of string s is representing as a node
Edges: each pair [i,j] is an edge between nodes i and j, indicating that characters at these indices can be swapped
Connected Components: All indices that are connected directly or indirectly through edges form a connected component
Within each connected component, you can freely rearrange the characters
Identifying Disjoint Sets in the Graph
a Disjoint Set (Union-Find (Disjoint Set)) is a collection of indices that belong to the same connected component in the graph
these sets represent groups of indices where swaps can occur
Algorithm Design
- graph representation
- treat indices of the string as nodes and pairs as edges to form an undirected graph
- find connected components
- ue union-find to efficiently group indicies into connected components
- collect and sort characters
- for each connected component, collect the characters from the string corresponding to the indices
- sort the characters lexicographically
- reassign sorted characters
- map the sorted characters back to the corresponding indices in the string
β Complexity Analysis
Time Complexity: O(n log n)
- sorting characters within components takes O(n log n)
- disjoint set operations
- initialising parent and rank structures takes O(n)
- union operation is O(Ξ±(n)) where Ξ± is the inverse ackermann function (almost constant)
Space Complexity: O(n)
- storage of indices in the disjoint set --> O(n)
- storage of connected components in a hashmap --> O(n)
π» Implementation of Solution
```cppclass UnionFind {private: vector<int> parent; // stores the parent of each element vector<int> rank; // stores the rank(depth) of each setpublic: // constructor initializes n elements with each element being its own parent UnionFind(int n) { parent.resize(n); rank.resize(n, 1); for (int i = 0; i < n; i++) { parent[i] = i; // Initially, each element is its own root } } int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // Path compression } return parent[x]; } void unionSets(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { // Union by Rank if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else { parent[rootY] = rootX; rank[rootX] += 1; } } }};class Solution {public: string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) { int n = s.size(); UnionFind uf(n); // step 1: union nodes based on pairs for(const auto& pair : pairs) { uf.unionSets(pair[0], pair[1]); } // step 2: group indices by their connected component unordered_map<int, vector<int>> components; for(int i=0; i < n; i++) { components[uf.find(i)].push_back(i); } // step 3: sort characters within each connected component for(auto& [root, indices] : components) { // collect characters string chars; for(int index : indices) { chars += s[index]; } // sort characters sort(chars.begin(), chars.end()); // reassign sorted characters back to their positions sort(indices.begin(), indices.end()); for(int i=0; i < indices.size(); i++) { s[indices[i]] = chars[i]; } } return s; }};