Python 0929.py
class Solution:
def numUniqueEmails(self, emails: List[str]) -> int:
def filter(mail):
# regex could be used, too
# dots
pos = mail.find(".")
while pos < mail.find("@"):
mail = mail[:pos] + mail[pos+1:]
pos = mail.find(".")
# plus
pos = mail.find("+")
at = mail.find("@")
if pos != -1 and pos < at:
mail = mail[:pos] + mail[at:]
return mail
return len(set(filter(m) for m in emails))
Difficulty
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 Solution class with a single public method numUniqueEmails. The method receives a list of email address strings and returns the count of distinct addresses after applying the normalization rules defined by the problem statement. Normalization consists of two transformations applied to the local part of each email: removal of all periods (.) and truncation at the first plus sign (+). The domain part (after @) remains unchanged. The code achieves this by defining an inner helper function filter that performs the transformations in place on a copy of the input string, then builds a set of the filtered results and returns its size.
Core Logic
Public Interface
def numUniqueEmails(self, emails: List[str]) -> int:
return len(set(filter(m) for m in emails))
The method iterates over the input list emails, applies the filter function to each element, and constructs a Python set from the generator expression. Because a set stores only unique elements, its length directly yields the number of distinct normalized email addresses.
Helper Function - filter
def filter(mail):
# dots
pos = mail.find(".")
while pos < mail.find("@"):
mail = mail[:pos] + mail[pos+1:]
pos = mail.find(".")
# plus
pos = mail.find("+")
at = mail.find("@")
if pos != -1 and pos < at:
mail = mail[:pos] + mail[at:]
return mail
The helper function receives a single email string mail. It performs two sequential phases:
-
Period Removal - The code locates the first period using
find. While the found position lies before the@symbol, the character at that position is omitted by concatenating the substring before the period with the substring after it. The search for the next period restarts from the beginning of the modified string. This loop continues until no period remains in the local part. -
Plus Truncation - After all periods are removed, the function searches for a plus sign. If a plus sign exists and appears before the
@, the string is sliced to keep everything up to the plus sign and then the domain part (starting at@). The portion after the plus sign is discarded.
The transformed string is returned to the caller.
Edge Cases Handled
The algorithm explicitly checks the relative positions of special characters with respect to the @ delimiter, ensuring that transformations affect only the local part.
When the email contains no periods, the initial while condition fails because mail.find(".") returns -1, which is not less than the index of @. Consequently, the loop body is skipped, leaving the string unchanged.
If a plus sign appears after the @ symbol, the condition pos < at prevents truncation, preserving the domain portion that may legitimately contain a plus sign.
Emails that lack a plus sign altogether cause mail.find("+") to return -1. The subsequent if statement evaluates to false, so the string is returned without truncation.
The code also gracefully handles emails where the local part is empty before the @ (e.g., +@example.com). The plus-truncation step will produce a string consisting solely of the domain, which is a valid normalized representation under the given rules.
When multiple periods appear consecutively, each iteration removes a single period, and the loop continues until none remain, effectively collapsing any sequence of periods.
If the email contains a period immediately before the @ (e.g., user.@example.com), the period is removed because the loop condition checks pos < mail.find("@"). The resulting local part ends with the character preceding the removed period, satisfying the rule that all periods are ignored.
Performance Optimizations
The implementation avoids the overhead of regular expression processing by using simple string search and slicing operations. Direct calls to find and slice concatenation are generally faster than compiling and applying a regex pattern, especially when processing a large list of emails.
By performing in-place reconstruction of the string during the period removal loop, the code eliminates the need for auxiliary data structures such as character arrays or lists. Each iteration creates a new string that is one character shorter, but the overall number of allocations is bounded by the number of periods in the local part, which is typically small.
The use of a generator expression inside the set constructor (set(filter(m) for m in emails)) ensures that each email is filtered lazily, avoiding the creation of an intermediate list of transformed emails. This reduces peak memory consumption, as only one transformed string exists at a time before being inserted into the set.
The final len call operates on the set, which provides O(1) average-time access to its size attribute, delivering the result without additional traversal.
Overall, the combination of targeted string operations, lazy evaluation, and direct set construction yields an efficient solution for counting unique normalized email addresses.
ELI5 - Simplified Explanation
Imagine you have a big pile of letters to deliver, but some of the addresses are written in a messy way. This code is like a little robot helper that tidies up the addresses so you know which ones are actually headed to the same person.
First, the robot looks at the name part of the email address before the at symbol. If it sees any tiny dots hiding in there, it picks them up and throws them away, because those dots do not change who is getting the mail.
Next, the robot looks for a plus sign. If it finds one, it knows that everything after the plus sign and before the at symbol is just extra notes, so it takes those notes and tosses them out. It keeps the part after the at symbol exactly as it is because that tells us which house the mail is going to.
Once the robot finishes cleaning up an address, it puts the result into a special box. The rule for this box is that it never keeps two of the same thing. If the robot cleans up two different looking addresses and they both turn out to be identical, the box only holds one of them. In the end, counting the items inside this box tells you how many different people are waiting for mail.
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 | JS | Go | Rust | TS | ||||
| LeetcodeHelp | C++ | Java | Python | ||||||||
| Progiez | C++ | Java | Python | ||||||||
| github | BigEggStudy | C# | |||||||||
| black-shadows | C++ | ||||||||||
| charles-wangkai | Java | ||||||||||
| DimitrisJim | C | ||||||||||
| everthis | JS | ||||||||||
| fartem | Ruby | ||||||||||
| fengvyi | C++ | ||||||||||
| fsq | C++ | ||||||||||
| haoel | C++ | ||||||||||
| JoshCrozier | JS | ||||||||||
| keineahnung2345 | C++ | ||||||||||
| Krazune | C++ | ||||||||||
| lkwq007 | Python | ||||||||||
| lzl124631x | C++ | ||||||||||
| qiyuangong | Java | ||||||||||
| suxrobGM | C# | ||||||||||
| varunu28 | Java | ||||||||||
| YuriSpiridonov | Python | ||||||||||
| AlgoMonster | C++, Python, Java, TypeScript | ||||||||||
| WalkCCC | C++, Python, Java | ||||||||||
| leetcode.ca | C++, Go, Java, JavaScript, Python, Rust, TypeScript | ||||||||||
| LeetcodeHelp | C++, Java, Python | ||||||||||
| Progiez | C++, Java, Python | ||||||||||
| BigEggStudy | C# | ||||||||||
| black-shadows | C++, C++ | ||||||||||
| charles-wangkai | Java | ||||||||||
| DimitrisJim | C | ||||||||||
| everthis | JavaScript | ||||||||||
| fartem | Ruby | ||||||||||
| fengvyi | C++ | ||||||||||
| fsq | C++ | ||||||||||
| haoel | C++ | ||||||||||
| JoshCrozier | JavaScript | ||||||||||
| keineahnung2345 | C++ | ||||||||||
| Krazune | C++ | ||||||||||
| lkwq007 | Python | ||||||||||
| lzl124631x | C++ | ||||||||||
| qiyuangong | Java | ||||||||||
| suxrobGM | C# | ||||||||||
| varunu28 | Java | ||||||||||
| YuriSpiridonov | Python |
Videos
Solved On The Same Day
- problem 1189: Maximum Number of Balloons
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