Unique Email Addresses
Count distinct inboxes by normalising each address, then storing the results in a set
Problem Statement
Every email address consists of a local name and a domain name, separated by @. Two rules apply to the local name only:
- Dots are ignored.
"alice.z@leetcode.com"and"alicez@leetcode.com"are the same inbox. - Everything from the first
+onward is ignored."m.y+name@a.com"forwards to"my@a.com".
Return the number of distinct addresses that actually receive mail.
Input: ["test.email+alex@leetcode.com",
"test.e.mail+bob.cathy@leetcode.com",
"testemail+david@lee.tcode.com"]
Output: 2
Why: the first two both normalise to "testemail@leetcode.com"
the third has a DIFFERENT domain: "testemail@lee.tcode.com"Intuition
Two things are happening: a normalisation step, and a de-duplication step.
The key insight
Convert each address into a canonical form, then put the canonical forms in a set. The set's size is the answer.
This is the same "canonical key" idea as Group Anagrams — turn "are these equivalent?" into "are these identical after normalisation?"
The rules apply to the local name only
The domain must be left completely untouched. "lee.tcode.com" is a different domain from "leetcode.com" — stripping dots there would merge two genuinely distinct addresses and give the wrong answer.
Splitting on @ before doing anything else is what keeps the two halves separate.
Order of operations
Strip the + suffix first, then remove dots. Doing it the other way also works here, but plus-first is safer: it means dots after the + are discarded along with everything else, rather than being processed and then thrown away.
Watch it run
Approach
Split each address on @
Split only on the first @ — the local name is guaranteed not to contain one, but being explicit avoids surprises.
Solution
def num_unique_emails(emails: list[str]) -> int:
"""Count how many distinct inboxes the addresses resolve to."""
seen: set[str] = set()
for email in emails:
local, domain = email.split("@", 1) # split on the FIRST @ only
local = local.split("+", 1)[0] # drop everything after the first +
local = local.replace(".", "") # dots in the local name mean nothing
seen.add(f"{local}@{domain}") # the domain is never modified
return len(seen)`split('@')` without a limit is risky
"a@b@c".split("@") gives three parts, and unpacking into two variables raises an error in Python. Passing maxsplit=1 (or using indexOf in TypeScript) splits on the first @ only and puts everything else in the domain — the behaviour real email parsing expects.
Why a set and not sorting
Sorting the normalised addresses and counting adjacent differences also works, at O(n log n). A hash set answers the same question in O(n) and expresses the intent directly: you care about distinctness, not order.
Complexity Analysis
Time Complexity
O(n × L)
Space Complexity
O(n × L)
Where n is the number of addresses and L is the average address length.
- Time — each address is scanned a constant number of times during normalisation.
- Space — the set holds up to
nnormalised strings.
Edge Cases
Related Problems
- Group Anagrams — the same canonical-form technique for grouping
- Contains Duplicate — sets used for distinctness
- Simplify Path — another string canonicalisation problem