831. Masking Personal Information
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.
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:
- Convert the entire string to lowercase:
s = s.lower()
- Find the position of '@' using
s.find('@')
- Construct the masked email by concatenating:
- First character:
s[0]
- Five asterisks:
'*****'
- Last character of name and everything after:
s[s.find('@') - 1:]
- First character:
This gives us the pattern: first_char + '*****' + last_char_of_name + '@' + domain
Step 3: Phone Number Processing
If it's a phone number:
-
Extract only digits using a generator expression with
isdigit()
:s = ''.join(c for c in s if c.isdigit())
-
Calculate the country code length:
cnt = len(s) - 10
This gives us 0, 1, 2, or 3 depending on total digits.
-
Create the suffix with masked local number:
suf = '***-***-' + s[-4:]
The last 4 digits
s[-4:]
are kept visible. -
Add country code prefix if needed:
- If
cnt == 0
: return justsuf
- If
cnt > 0
: returnf'+{"*" * cnt}-{suf}'
The f-string
f'+{"*" * cnt}-{suf}'
dynamically creates the right number of asterisks for the country code. - If
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 EvaluatorExample Walkthrough
Let's walk through two examples to illustrate the solution approach:
Example 1: Email - "Alice@Gmail.com"
- Check first character 'A' - it's alphabetic, so this is an email
- Convert to lowercase:
"alice@gmail.com"
- Find '@' position: index 5
- Build masked email:
- First character:
'a'
- Add five asterisks:
'*****'
- Last character before '@' (index 4):
'e'
- Everything from '@' onwards:
'@gmail.com'
- First character:
- Result:
"a*****e@gmail.com"
Example 2: Phone Number - "1(234)567-890"
- Check first character '1' - it's not alphabetic, so this is a phone number
- Extract only digits:
"1234567890"
(10 digits total) - Calculate country code length:
10 - 10 = 0
(no country code) - Build masked number:
- Take last 4 digits:
"7890"
- Create suffix:
"***-***-7890"
- Since country code length is 0, no prefix needed
- Take last 4 digits:
- Result:
"***-***-7890"
Example 3: Phone Number with Country Code - "86-(10)12345678"
- Check first character '8' - it's not alphabetic, so this is a phone number
- Extract only digits:
"861012345678"
(12 digits total) - Calculate country code length:
12 - 10 = 2
(2-digit country code) - Build masked number:
- Take last 4 digits:
"5678"
- Create suffix:
"***-***-5678"
- Country code length is 2, so add prefix:
"+**-"
- Take last 4 digits:
- 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()
takesO(1)
time - For email masking:
s.lower()
takesO(n)
time,s.find('@')
takesO(n)
time, and string slicing takesO(n)
time - For phone masking: The list comprehension
''.join(c for c in s if c.isdigit())
iterates through all characters once, takingO(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 sizeO(n)
, and the result string has sizeO(n)
- For phone masking: The filtered digit string created by
''.join(c for c in s if c.isdigit())
requiresO(n)
space in the worst case, and the final formatted string requiresO(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...
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!