A clear explanation of the Masking Personal Information problem using string parsing and format-specific masking rules.
Problem Restatement
We are given a string s.
The string represents either:
| Type | Example |
|---|---|
| Email address | "[email protected]" |
| Phone number | "1(234)567-890" |
We need to return the masked version of the personal information.
The masking rules are different for emails and phone numbers.
An email contains the character "@".
A phone number contains digits and separator characters.
Input and Output
| Item | Meaning |
|---|---|
| Input | A personal information string s |
| Output | The masked personal information |
| Email rule | Lowercase all letters, keep first and last character of the name, replace middle with 5 stars |
| Phone rule | Keep only last 4 digits visible |
| Phone digits | A phone number contains 10 to 13 digits |
Function shape:
class Solution:
def maskPII(self, s: str) -> str:
...Examples
Example 1:
s = "[email protected]"Lowercase it:
The name before "@" is:
"leetcode"Keep the first and last character of the name:
"l" and "e"Replace the middle with exactly five stars:
"l*****[email protected]"So the answer is:
"l*****[email protected]"Example 2:
s = "[email protected]"Lowercase it:
The name is "ab".
There are no middle characters, but the rule still uses exactly five stars.
So the answer is:
"a*****[email protected]"Example 3:
s = "1(234)567-890"The digits are:
"1234567890"There are exactly 10 digits, so there is no country code.
Keep only the last 4 digits:
"7890"The answer is:
"***-***-7890"Example 4:
s = "86-(10)12345678"The digits are:
"861012345678"There are 12 digits.
The last 10 digits are the local number.
The first 2 digits are the country code.
So the masked country code has two stars:
"+**-"The answer is:
"+**-***-***-5678"First Thought: Handle Both Formats Separately
This problem is mostly string formatting.
There are two separate cases:
- Email address
- Phone number
We can detect an email by checking whether "@" exists in s.
If it is an email, lowercase the whole string and build the masked email.
If it is a phone number, extract only digits and build the masked phone string.
Key Insight
The input is guaranteed valid.
So we do not need to validate whether the email or phone number is legal.
We only need to apply the required formatting.
For email:
first_character + "*****" + last_character_before_at + rest_after_last_characterFor phone:
| Digit count | Country code length | Output format |
|---|---|---|
10 | 0 | "***-***-1234" |
11 | 1 | "+*-***-***-1234" |
12 | 2 | "+**-***-***-1234" |
13 | 3 | "+***-***-***-1234" |
Algorithm
Check whether the string contains "@".
If yes:
- Convert
sto lowercase. - Find the index of
"@". - Return:
s[0] + "*****" + s[at_index - 1:]
If no:
- Extract all digits from
s. - Let
digitsbe the resulting digit string. - Let
localbe the last 4 digits. - If there are exactly 10 digits, return:
"***-***-" + local - Otherwise, the country code length is:
len(digits) - 10 - Return:
"+" + "*" * country_length + "-***-***-" + local
Walkthrough
Use:
s = "86-(10)12345678"There is no "@", so this is a phone number.
Extract digits:
digits = "861012345678"The number of digits is:
12So the country code length is:
12 - 10 = 2The last 4 digits are:
"5678"Build the result:
"+" + "**" + "-***-***-" + "5678"which gives:
"+**-***-***-5678"Correctness
There are two valid input types: email and phone number.
If the string contains "@", it is an email. The algorithm lowercases the entire string, keeps the first character of the name, replaces the middle of the name with exactly five stars, and appends from the last character of the name through the domain. This matches the required email masking rule.
If the string does not contain "@", it is a phone number. The algorithm removes all separator characters by keeping only digits. The last 10 digits are the local number, and all earlier digits form the country code.
The algorithm always exposes only the last 4 digits. It formats the local number as:
***-***-XXXXwhere XXXX is the last 4 digits.
If a country code exists, the algorithm prefixes the local format with "+", one star for each country-code digit, and "-". This matches the required phone masking rule.
Therefore, in both possible input cases, the algorithm returns exactly the required masked string.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the string to lowercase or extract digits |
| Space | O(n) | We build the output string and, for phones, the digit string |
Here n is the length of s.
Implementation
class Solution:
def maskPII(self, s: str) -> str:
if "@" in s:
s = s.lower()
at_index = s.index("@")
return s[0] + "*****" + s[at_index - 1:]
digits = []
for ch in s:
if ch.isdigit():
digits.append(ch)
digits = "".join(digits)
last_four = digits[-4:]
if len(digits) == 10:
return "***-***-" + last_four
country_length = len(digits) - 10
return "+" + "*" * country_length + "-***-***-" + last_fourCode Explanation
For email detection:
if "@" in s:The problem guarantees valid input, so this cleanly separates email from phone number.
For email masking:
s = s.lower()
at_index = s.index("@")
return s[0] + "*****" + s[at_index - 1:]at_index - 1 is the last character of the name before "@".
So:
s[at_index - 1:]includes the last name character, the "@", and the full lowercase domain.
For phone numbers, we extract digits:
digits = []
for ch in s:
if ch.isdigit():
digits.append(ch)Then:
digits = "".join(digits)
last_four = digits[-4:]keeps the visible part.
If there is no country code:
if len(digits) == 10:
return "***-***-" + last_fourOtherwise:
country_length = len(digits) - 10
return "+" + "*" * country_length + "-***-***-" + last_fouradds the country-code prefix.
Testing
def run_tests():
s = Solution()
assert s.maskPII("[email protected]") == "l*****[email protected]"
assert s.maskPII("[email protected]") == "a*****[email protected]"
assert s.maskPII("1(234)567-890") == "***-***-7890"
assert s.maskPII("86-(10)12345678") == "+**-***-***-5678"
assert s.maskPII("+1(234)567-890") == "+*-***-***-7890"
assert s.maskPII("+(501321)-50-23431") == "+***-***-***-3431"
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Mixed-case email | Confirms lowercasing |
| Short email name | Confirms exactly five stars |
| 10-digit phone | Confirms no country code |
| 12-digit phone | Confirms two-digit country code |
| 11-digit phone | Confirms one-digit country code |
| 13-digit phone | Confirms three-digit country code |