Facebook Pixel

831. Masking Personal Information

MediumString
Leetcode Link

Problem Description

This problem asks you to mask personal information in a given string s, which can be either an email address or a phone number.

For Email Addresses:

  • An email consists of a name (letters), followed by '@', followed by a domain (letters with a dot somewhere in the middle)
  • To mask it:
    • Convert all letters to lowercase
    • Keep the first and last letters of the name
    • Replace all middle letters of the name with exactly 5 asterisks "*****"
    • Keep everything from the last letter of the name onwards (including '@' and domain)

For example: "LeetCode@LeetCode.com" becomes "l*****e@leetcode.com"

For Phone Numbers:

  • A phone number contains 10-13 digits total
  • The last 10 digits are the local number
  • The first 0-3 digits (if any) are the country code
  • Various separation characters like '+', '-', '(', ')', ' ' may be present
  • To mask it:
    • Remove all non-digit characters
    • Keep only the last 4 digits visible
    • Replace other local digits with asterisks in the format "***-***-XXXX"
    • If there's a country code (1-3 digits), add it as "+*-", "+**-", or "+***-" prefix

For example:

  • "1(234)567-890" (10 digits, no country code) becomes "***-***-7890"
  • "86-(10)12345678" (12 digits, 2-digit country code) becomes "+**-***-***-5678"

The solution identifies whether the input is an email (starts with a letter) or phone number, then applies the appropriate masking rules. For emails, it constructs the masked string by keeping the first character, adding 5 asterisks, and appending everything from the last character of the name onwards. For phone numbers, it extracts all digits, determines the country code length, and formats the output accordingly with the last 4 digits visible.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to handle two completely different formats, so the first step is to distinguish between them. Since email addresses must start with a letter and phone numbers contain digits, we can check if the first character is alphabetic to determine the type.

For email masking, we observe that:

  • We need to preserve only the first and last characters of the name portion
  • Everything after the '@' symbol remains unchanged (except for case conversion)
  • The middle portion is always replaced with exactly 5 asterisks, regardless of the actual length

This leads us to find the position of '@', then construct our result by concatenating: first character + "*****" + last character before '@' + everything from '@' onwards. Converting to lowercase can be done upfront for simplicity.

For phone number masking, we notice:

  • All non-digit characters are just separators that should be removed
  • The output format is fixed based on the total number of digits
  • The last 4 digits are always visible, and the 6 digits before them are always masked as "***-***"
  • The variable part is only the country code prefix

After extracting just the digits, we can calculate how many digits exceed 10 (which gives us the country code length: 0-3). Based on this count:

  • 0 extra digits: no country code prefix needed
  • 1-3 extra digits: add "+" followed by the corresponding number of asterisks

This approach naturally handles all cases by building the suffix "***-***-XXXX" with the last 4 digits, then conditionally adding the country code prefix based on the digit count.

Solution Approach

The implementation follows a straightforward simulation approach by handling emails and phone numbers separately.

Step 1: Determine the input type

We check if the first character is alphabetic using s[0].isalpha(). If true, it's an email; otherwise, it's a phone number.

Step 2: Email Processing

If it's an email:

  1. Convert the entire string to lowercase: s = s.lower()
  2. Find the position of '@' using s.find('@')
  3. Construct the masked email by concatenating:
    • First character: s[0]
    • Five asterisks: '*****'
    • Last character of name and everything after: s[s.find('@') - 1:]

This gives us the pattern: first_char + '*****' + last_char_of_name + '@' + domain

Step 3: Phone Number Processing

If it's a phone number:

  1. Extract only digits using a generator expression with isdigit():

    s = ''.join(c for c in s if c.isdigit())
  2. Calculate the country code length:

    cnt = len(s) - 10

    This gives us 0, 1, 2, or 3 depending on total digits.

  3. Create the suffix with masked local number:

    suf = '***-***-' + s[-4:]

    The last 4 digits s[-4:] are kept visible.

  4. Add country code prefix if needed:

    • If cnt == 0: return just suf
    • If cnt > 0: return f'+{"*" * cnt}-{suf}'

    The f-string f'+{"*" * cnt}-{suf}' dynamically creates the right number of asterisks for the country code.

Time Complexity: O(n) where n is the length of the input string, as we traverse it once to process.

Space Complexity: O(n) for creating the output string and temporary string when filtering digits.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through two examples to illustrate the solution approach:

Example 1: Email - "Alice@Gmail.com"

  1. Check first character 'A' - it's alphabetic, so this is an email
  2. Convert to lowercase: "alice@gmail.com"
  3. Find '@' position: index 5
  4. Build masked email:
    • First character: 'a'
    • Add five asterisks: '*****'
    • Last character before '@' (index 4): 'e'
    • Everything from '@' onwards: '@gmail.com'
  5. Result: "a*****e@gmail.com"

Example 2: Phone Number - "1(234)567-890"

  1. Check first character '1' - it's not alphabetic, so this is a phone number
  2. Extract only digits: "1234567890" (10 digits total)
  3. Calculate country code length: 10 - 10 = 0 (no country code)
  4. Build masked number:
    • Take last 4 digits: "7890"
    • Create suffix: "***-***-7890"
    • Since country code length is 0, no prefix needed
  5. Result: "***-***-7890"

Example 3: Phone Number with Country Code - "86-(10)12345678"

  1. Check first character '8' - it's not alphabetic, so this is a phone number
  2. Extract only digits: "861012345678" (12 digits total)
  3. Calculate country code length: 12 - 10 = 2 (2-digit country code)
  4. Build masked number:
    • Take last 4 digits: "5678"
    • Create suffix: "***-***-5678"
    • Country code length is 2, so add prefix: "+**-"
  5. Result: "+**-***-***-5678"

Solution Implementation

1class Solution:
2    def maskPII(self, s: str) -> str:
3        """
4        Masks personally identifiable information (PII) in a string.
5        Handles both email addresses and phone numbers.
6      
7        Args:
8            s: Input string containing either an email or phone number
9          
10        Returns:
11            Masked string with sensitive information hidden
12        """
13      
14        # Check if input is an email (starts with a letter)
15        if s[0].isalpha():
16            # Convert email to lowercase for standardization
17            s = s.lower()
18          
19            # Find the position of '@' symbol
20            at_position = s.find('@')
21          
22            # Return masked email: first_char + ***** + last_char + @domain
23            return s[0] + '*****' + s[at_position - 1:]
24      
25        # Handle phone number case
26        # Extract only digits from the string
27        digits_only = ''.join(char for char in s if char.isdigit())
28      
29        # Calculate country code length (total digits minus 10 local digits)
30        country_code_length = len(digits_only) - 10
31      
32        # Format the last 4 digits with standard masking pattern
33        suffix = '***-***-' + digits_only[-4:]
34      
35        # Return formatted phone number with or without country code
36        if country_code_length == 0:
37            # No country code, return local format
38            return suffix
39        else:
40            # Include country code with appropriate masking
41            return f'+{"*" * country_code_length}-{suffix}'
42
1class Solution {
2    public String maskPII(String s) {
3        // Check if the input is an email (starts with a letter)
4        if (Character.isLetter(s.charAt(0))) {
5            // Convert email to lowercase
6            s = s.toLowerCase();
7          
8            // Find the position of '@' symbol
9            int atIndex = s.indexOf('@');
10          
11            // Return masked email: first letter + 5 asterisks + last letter before @ + rest of email
12            return s.substring(0, 1) + "*****" + s.substring(atIndex - 1);
13        }
14      
15        // Handle phone number case
16        // Extract only digits from the phone number
17        StringBuilder digitsBuilder = new StringBuilder();
18        for (char c : s.toCharArray()) {
19            if (Character.isDigit(c)) {
20                digitsBuilder.append(c);
21            }
22        }
23      
24        // Get the string of digits only
25        String digitsOnly = digitsBuilder.toString();
26      
27        // Calculate the number of country code digits (total digits minus 10 local digits)
28        int countryCodeLength = digitsOnly.length() - 10;
29      
30        // Create the suffix with last 4 digits visible
31        String maskedSuffix = "***-***-" + digitsOnly.substring(digitsOnly.length() - 4);
32      
33        // Return formatted phone number based on whether there's a country code
34        if (countryCodeLength == 0) {
35            // No country code, return local format
36            return maskedSuffix;
37        } else {
38            // Has country code, add prefix with masked country code
39            return "+" + "*".repeat(countryCodeLength) + "-" + maskedSuffix;
40        }
41    }
42}
43
1class Solution {
2public:
3    string maskPII(string s) {
4        // Check if the input is an email (contains '@')
5        int atPosition = s.find('@');
6      
7        if (atPosition != -1) {
8            // Handle email masking
9            string maskedEmail;
10          
11            // Add first character in lowercase
12            maskedEmail += tolower(s[0]);
13          
14            // Add five asterisks as mask
15            maskedEmail += "*****";
16          
17            // Add last character before '@' and everything after (including '@') in lowercase
18            for (int j = atPosition - 1; j < s.size(); ++j) {
19                maskedEmail += tolower(s[j]);
20            }
21          
22            return maskedEmail;
23        }
24      
25        // Handle phone number masking
26        // Extract only digits from the input string
27        string digitsOnly;
28        for (char c : s) {
29            if (isdigit(c)) {
30                digitsOnly += c;
31            }
32        }
33      
34        // Calculate country code length (total digits minus 10 local digits)
35        int countryCodeLength = digitsOnly.size() - 10;
36      
37        // Create suffix with last 4 digits: "***-***-XXXX"
38        string maskedSuffix = "***-***-" + digitsOnly.substr(digitsOnly.size() - 4);
39      
40        // If no country code, return just the masked local number
41        // Otherwise, add country code prefix with appropriate number of asterisks
42        if (countryCodeLength == 0) {
43            return maskedSuffix;
44        } else {
45            return "+" + string(countryCodeLength, '*') + "-" + maskedSuffix;
46        }
47    }
48};
49
1/**
2 * Masks personally identifiable information (PII) in a string.
3 * Handles two types of PII: email addresses and phone numbers.
4 * 
5 * @param s - The input string containing either an email or phone number
6 * @returns The masked version of the input string
7 */
8function maskPII(s: string): string {
9    // Check if the input is an email by looking for '@' symbol
10    const atSymbolIndex = s.indexOf('@');
11  
12    if (atSymbolIndex !== -1) {
13        // Handle email masking
14        // Keep first letter, add 5 asterisks, then add from last letter before @ to end
15        let maskedEmail = s[0].toLowerCase() + '*****';
16      
17        // Append the last character before '@' and everything after, in lowercase
18        for (let j = atSymbolIndex - 1; j < s.length; ++j) {
19            maskedEmail += s.charAt(j).toLowerCase();
20        }
21      
22        return maskedEmail;
23    }
24  
25    // Handle phone number masking
26    // Extract only digits from the input string
27    let digitsOnly = '';
28    for (const character of s) {
29        if (/\d/.test(character)) {
30            digitsOnly += character;
31        }
32    }
33  
34    // Calculate the number of digits beyond the standard 10-digit phone number
35    const extraDigitsCount = digitsOnly.length - 10;
36  
37    // Create the suffix with the last 4 digits visible
38    const phoneSuffix = `***-***-${digitsOnly.substring(digitsOnly.length - 4)}`;
39  
40    // If no extra digits, return the standard format
41    // Otherwise, add country code prefix with appropriate masking
42    return extraDigitsCount === 0 
43        ? phoneSuffix 
44        : `+${'*'.repeat(extraDigitsCount)}-${phoneSuffix}`;
45}
46

Time and Space Complexity

The time complexity is O(n), where n is the length of the input string s. This is because:

  • The check s[0].isalpha() takes O(1) time
  • For email masking: s.lower() takes O(n) time, s.find('@') takes O(n) time, and string slicing takes O(n) time
  • For phone masking: The list comprehension ''.join(c for c in s if c.isdigit()) iterates through all characters once, taking O(n) time
  • String concatenation and formatting operations take O(n) time in the worst case

The space complexity is O(n), where n is the length of the input string s. This is because:

  • For email masking: s.lower() creates a new string of size O(n), and the result string has size O(n)
  • For phone masking: The filtered digit string created by ''.join(c for c in s if c.isdigit()) requires O(n) space in the worst case, and the final formatted string requires O(n) space

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Email Name Length Assumption

A critical pitfall is assuming the email name always has more than 2 characters. If an email has only 1 or 2 characters before '@', the current approach of using s[0] + '*****' + s[at_position - 1:] could produce incorrect results.

Example Issue:

  • Input: "a@leetcode.com" (single character name)
  • Current output: "a*****a@leetcode.com" (incorrect - duplicates the character)
  • Expected: "a*****a@leetcode.com" (technically correct but redundant)

Solution:

# Check the length of the email name
at_position = s.find('@')
name_length = at_position

if name_length == 1:
    # Single character - no masking needed
    return s[0] + '@' + s[at_position + 1:]
elif name_length == 2:
    # Two characters - still use 5 asterisks per problem requirement
    return s[0] + '*****' + s[1:]
else:
    # Standard case
    return s[0] + '*****' + s[at_position - 1:]

2. Not Handling Edge Cases in Phone Number Digit Count

The code assumes the phone number will always have 10-13 digits. If the input has fewer than 10 digits or more than 13, the current logic breaks down.

Example Issue:

  • Input with 9 digits: "123-456-789"
  • Current behavior: country_code_length = -1, causing incorrect formatting

Solution:

# Validate digit count before processing
digits_only = ''.join(char for char in s if char.isdigit())

if len(digits_only) < 10 or len(digits_only) > 13:
    # Handle invalid phone number
    raise ValueError(f"Invalid phone number: must have 10-13 digits, got {len(digits_only)}")

country_code_length = len(digits_only) - 10
# Continue with existing logic...

3. String Concatenation Performance in Large Inputs

Using string concatenation with ''.join() for filtering digits is good, but the initial check s[0].isalpha() could fail on empty strings.

Example Issue:

  • Input: "" (empty string)
  • Current behavior: IndexError: string index out of range

Solution:

def maskPII(self, s: str) -> str:
    # Add empty string check
    if not s:
        return s
  
    # Now safe to check s[0]
    if s[0].isalpha():
        # Email processing...
    else:
        # Phone processing...

4. Ambiguous Character Classification

The code assumes if the first character isn't alphabetic, it must be a phone number. This could fail with malformed inputs starting with special characters that aren't valid phone number separators.

Example Issue:

  • Input: "#invalid"
  • Current behavior: Treats as phone number, returns "***-***-"

Solution:

# More robust input validation
if s[0].isalpha():
    # Email processing
    if '@' not in s:
        raise ValueError("Invalid email: missing @ symbol")
    # Continue email processing...
else:
    # Validate it's actually a phone number format
    digits_only = ''.join(char for char in s if char.isdigit())
    if not digits_only:
        raise ValueError("Invalid phone number: no digits found")
    # Continue phone processing...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More