Back to blog
Jul 16, 2024
4 min read

Unique Email Addresses

Count unique email addresses after applying rules: ignore dots in local name and ignore everything after a plus sign.

Difficulty: Easy | Acceptance: 67.90% | Paid: No Topics: Array, Hash Table, String

Every valid email consists of a local name and a domain name, separated by the ’@’ sign.

Besides lowercase letters, these emails may contain one or more ’.’ or ’+‘.

If you add periods ’.’ between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name.

Note that this rule does not apply to domain names.

If you add a plus ’+’ in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered. Note that this rule does not apply to domain names.

It is possible to use both of these rules at the same time.

Given a list of emails, we send one email to each address in the list. How many different addresses actually receive mails?

Examples

Example 1:

Input: emails = ["test.email+alex@leetcode.com","test.e.mail+bob.cathy@leetcode.com","testemail+david@lee.tcode.com"]
Output: 2
Explanation: "testemail@leetcode.com" and "testemail@lee.tcode.com" actually receive mails.

Example 2:

Input: emails = ["a@leetcode.com","b@leetcode.com","c@leetcode.com"]
Output: 3

Constraints

1 <= emails.length <= 100
1 <= emails[i].length <= 100
emails[i] consist of lowercase English letters, '+', '.' and '@'.
Each emails[i] contains exactly one '@' character.
All local and domain names are non-empty.
Local names do not start with a '+' character.
Domain names end with ".com".

String Parsing

Intuition We can process each email by splitting it into local and domain parts. For the local part, we remove all ’.’ and ignore any characters following the first ’+‘. The domain part remains unchanged. We then combine the processed local part with the domain and store the result in a set to automatically handle uniqueness.

Steps

  • Initialize an empty set to store unique email addresses.
  • Iterate through each email in the input list.
  • Split the email into local and domain parts using the ’@’ delimiter.
  • Find the index of ’+’ in the local part. If it exists, truncate the local part up to that index.
  • Remove all occurrences of ’.’ from the processed local part.
  • Concatenate the cleaned local part, ’@’, and the domain part.
  • Add the resulting string to the set.
  • Return the size of the set.
python
class Solution:
    def numUniqueEmails(self, emails: list[str]) -&gt; int:
        unique = set()
        for email in emails:
            local, domain = email.split('@')
            # Ignore everything after '+'
            if '+' in local:
                local = local[:local.index('+')]
            # Remove all '.'
            local = local.replace('.', '')
            unique.add(local + '@' + domain)
        return len(unique)

Complexity

  • Time: O(N * L) where N is the number of emails and L is the maximum length of an email.
  • Space: O(N * L) to store the unique emails in the set.
  • Notes: Using built-in string functions makes the code concise and readable.

Iterative Character Processing

Intuition Instead of creating multiple intermediate strings via split and replace, we can iterate through each character of the email once. We build the normalized email character by character. This approach avoids the overhead of creating multiple string objects and is more memory efficient in languages where strings are immutable.

Steps

  • Initialize an empty set to store unique email addresses.
  • Iterate through each email in the input list.
  • Initialize a string builder (or list) to construct the normalized email.
  • Iterate through each character of the email:
    • If the character is ’@’, append the rest of the email (domain) to the builder and break.
    • If the character is ’+’, skip all characters until the ’@’ is found.
    • If the character is ’.’, skip it.
    • Otherwise, append the character to the builder.
  • Add the constructed string to the set.
  • Return the size of the set.
python
class Solution:
    def numUniqueEmails(self, emails: list[str]) -&gt; int:
        unique = set()
        for email in emails:
            clean = []
            i = 0
            while i &lt; len(email):
                if email[i] == '@':
                    clean.append(email[i:])
                    break
                elif email[i] == '+':
                    while email[i] != '@':
                        i += 1
                    clean.append(email[i:])
                    break
                elif email[i] == '.':
                    i += 1
                else:
                    clean.append(email[i])
                    i += 1
            unique.add(''.join(clean))
        return len(unique)

Complexity

  • Time: O(N * L) where N is the number of emails and L is the maximum length of an email.
  • Space: O(N * L) to store the unique emails in the set.
  • Notes: This approach minimizes string allocations and is generally more performant for large inputs.