Python 0893.py
class Solution:
def numSpecialEquivGroups(self, words: List[str]) -> int:
have = set()
for w in words:
even = sorted(w[ ::2])
odd = sorted(w[1::2])
both = "".join(even + odd)
have.add(both)
return len(have)
Difficulty
and has an acceptance rate of about 74% (80k+ submissions).
Typical patterns to solve this problem:
- Array
- Hash Table
- Sorting
- String
AI Disclaimer
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.
See this dedicated page for more details.
Code Walkthrough
Overview
The provided implementation defines a class Solution with a single method numSpecialEquivGroups. The method receives a list of strings (words) and returns the count of distinct groups of words that are special-equivalent. Two words belong to the same group when one can be transformed into the other by any number of swaps of characters at even indices with other even indices, and similarly for odd indices. The algorithm normalizes each word by sorting the characters that appear at even positions separately from those at odd positions, concatenates the two sorted sequences, and stores the resulting canonical representation in a set. The final answer is the size of that set.
Key Steps
The core of the algorithm consists of three logical actions performed for each word in the input list.
Normalizing the even-position characters
even = sorted(w[::2])
The slice w[::2] extracts every character whose index is even (0, 2, 4, …). By applying sorted, the characters are placed in a deterministic order, eliminating any variation caused by permissible swaps among even positions. The resulting list even serves as the even-position component of the word’s canonical form.
Normalizing the odd-position characters
odd = sorted(w[1::2])
Analogously, the slice w[1::2] captures characters at odd indices (1, 3, 5, …). Sorting this sub-list yields a stable ordering for the odd-position component, neutralizing the effect of swaps that are allowed only among odd indices.
Building the canonical representation and collecting unique groups
both = "".join(even + odd)
have.add(both)
The two sorted lists are concatenated, and join converts the combined sequence into a single string. This string uniquely identifies the equivalence class of the original word because any two words that are special-equivalent will produce identical both values after the sorting steps. The set have accumulates these unique identifiers, automatically discarding duplicates.
Returning the count of distinct groups
return len(have)
After processing all input words, the size of the set reflects the number of distinct special-equivalence groups, which is the required result.
Edge Cases Handled
The algorithm gracefully handles several edge conditions without additional branching logic.
When a word is empty (""), both slices w[::2] and w[1::2] produce empty lists. Sorting empty lists yields empty lists, and concatenating them results in an empty string. Adding this empty string to the set correctly counts the empty word as a single group.
If a word consists of a single character, the even slice captures that character while the odd slice is empty. The canonical form becomes the single character itself, ensuring that any other single-character word with the same letter maps to the same group.
Words of odd length generate an even slice that is one element longer than the odd slice. The sorting and concatenation steps remain valid because each slice is processed independently, and the final string reflects the exact multiset of characters at even and odd positions.
The use of a set automatically eliminates duplicate canonical forms, so repeated words in the input list do not inflate the group count. This behavior also covers the case where all words are identical; the set will contain a single entry, and the method will return 1.
Performance Optimizations
The implementation leverages Python’s built-in data structures and operations to achieve efficient execution.
The slice notation (w[::2] and w[1::2]) extracts the required characters in linear time without explicit loops, allowing the interpreter to handle the indexing internally.
Sorting is performed on the two short sub-lists rather than on the entire word. For a word of length n, each slice contains roughly n/2 elements, so the total sorting work per word is comparable to sorting the whole word but benefits from the reduced constant factor of handling smaller lists.
The concatenation of the two sorted lists using the + operator creates a new list of length n before converting it to a string. This approach avoids repeated string concatenations, which would be costly due to immutability. By building the final string in a single join call, the code minimizes memory allocations.
Storing canonical forms in a set provides O(1) average-time insertion and membership checks, ensuring that the overall processing time scales linearly with the number of input words. The final len operation on the set is constant-time.
Collectively, these choices keep the algorithm’s runtime proportional to the total number of characters across all input words, while memory usage is bounded by the number of distinct groups, which is at most the number of input words.
ELI5 - Simplified Explanation
Imagine a big box of magical word blocks. Some words look different but are actually part of the same group because their letters can be swapped around like pieces in a puzzle. The computer looks at every word in the box and splits it into two piles. One pile gets the letters at the beginning, second, fourth, and sixth spots, and the other pile gets the rest. By putting those letters in order from shortest to tallest, the computer gives the word a secret special nickname. If two words end up with the same secret nickname, they belong to the same group. A special container keeps track of how many unique nicknames are found so everything stays neat and tidy. The final answer tells how many different groups are hiding inside the box.
Code Quality
Complexity Analysis
As a rule of thumb, the maximum input size for one second execution time is
- factorial: up to 10 elements
- exponential: up to 20 elements
- polynomial: a few thousand elements
- loglinear: up to 100,000 elements
- linear: a few million elements
- logarithmic: a billion elements
- constant: practically unlimited
Other Solutions
Obviously, I did that after I solved the problem myself.
In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.
And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.
(click on a column header to sort the table)
| author | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| author | solutions | ||||||||
| www | AlgoMonster | C++ | Java | Python | TS | ||||
| WalkCCC | C++ | Java | Python | ||||||
| leetcode.ca | C++ | Java | Python | Go | |||||
| LeetcodeHelp | C++ | Java | Python | ||||||
| Progiez | C++ | Java | Python | ||||||
| github | BigEggStudy | C# | |||||||
| black-shadows | C++ | ||||||||
| charles-wangkai | Java | ||||||||
| everthis | JS | ||||||||
| gouthampradhan | Java | ||||||||
| JoshCrozier | JS | ||||||||
| keineahnung2345 | C++ | ||||||||
| lkwq007 | Python | ||||||||
| lzl124631x | C++ | ||||||||
| ruoru | Rust | ||||||||
| suxrobGM | C# | ||||||||
| varunu28 | Java | ||||||||
| zfang399 | C++ | ||||||||
| AlgoMonster | C++, Python, Java, TypeScript | ||||||||
| WalkCCC | C++, Python, Java | ||||||||
| leetcode.ca | C++, Go, Java, Python | ||||||||
| LeetcodeHelp | C++, Java, Python | ||||||||
| Progiez | C++, Java, Python | ||||||||
| BigEggStudy | C# | ||||||||
| black-shadows | C++, C++ | ||||||||
| charles-wangkai | Java | ||||||||
| everthis | JavaScript | ||||||||
| gouthampradhan | Java | ||||||||
| JoshCrozier | JavaScript | ||||||||
| keineahnung2345 | C++ | ||||||||
| lkwq007 | Python | ||||||||
| lzl124631x | C++ | ||||||||
| ruoru | Rust | ||||||||
| suxrobGM | C# | ||||||||
| varunu28 | Java | ||||||||
| zfang399 | C++ |
Videos
Solved On The Same Day
- problem 2069: Walking Robot Simulation II
- problem 2070: Most Beautiful Item for Each Query
- problem 2498: Frog Jump II
- problem 460: LFU Cache
Extra
- Tech Stack - my development environment and tools
- AI - how AI helped me create this website (NOT solving the problems)
- Progress - progression over time
- Quality - code quality