Skip to content

LeetCode 831: Masking Personal Information

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:

TypeExample
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

ItemMeaning
InputA personal information string s
OutputThe masked personal information
Email ruleLowercase all letters, keep first and last character of the name, replace middle with 5 stars
Phone ruleKeep only last 4 digits visible
Phone digitsA phone number contains 10 to 13 digits

Function shape:

class Solution:
    def maskPII(self, s: str) -> str:
        ...

Examples

Example 1:

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:

So the answer is:

Example 2:

Lowercase it:

The name is "ab".

There are no middle characters, but the rule still uses exactly five stars.

So the answer is:

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:

  1. Email address
  2. 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_character

For phone:

Digit countCountry code lengthOutput format
100"***-***-1234"
111"+*-***-***-1234"
122"+**-***-***-1234"
133"+***-***-***-1234"

Algorithm

Check whether the string contains "@".

If yes:

  1. Convert s to lowercase.
  2. Find the index of "@".
  3. Return:
    s[0] + "*****" + s[at_index - 1:]

If no:

  1. Extract all digits from s.
  2. Let digits be the resulting digit string.
  3. Let local be the last 4 digits.
  4. If there are exactly 10 digits, return:
    "***-***-" + local
  5. Otherwise, the country code length is:
    len(digits) - 10
  6. 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:

12

So the country code length is:

12 - 10 = 2

The 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:

***-***-XXXX

where 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

MetricValueWhy
TimeO(n)We scan the string to lowercase or extract digits
SpaceO(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_four

Code 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_four

Otherwise:

country_length = len(digits) - 10
return "+" + "*" * country_length + "-***-***-" + last_four

adds 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:

TestWhy
Mixed-case emailConfirms lowercasing
Short email nameConfirms exactly five stars
10-digit phoneConfirms no country code
12-digit phoneConfirms two-digit country code
11-digit phoneConfirms one-digit country code
13-digit phoneConfirms three-digit country code