Facebook Pixel

1694. Reformat Phone Number

EasyString
Leetcode Link

Problem Description

You need to reformat a phone number string that contains digits, spaces, and dashes into a standardized format.

The reformatting process follows these steps:

  1. Remove all spaces and dashes from the input string, keeping only the digits.

  2. Group the digits from left to right into blocks of length 3, continuing until you have 4 or fewer digits remaining.

  3. Handle the remaining digits based on how many are left:

    • If 2 digits remain: Keep them as a single block of length 2
    • If 3 digits remain: Keep them as a single block of length 3
    • If 4 digits remain: Split them into two blocks of length 2 each
  4. Join all blocks with dashes between them.

The key constraints are:

  • Never create blocks of length 1
  • Create at most two blocks of length 2
  • All other blocks should be of length 3

For example:

  • Input: "1-23-45 6" becomes "123-456" (two blocks of 3)
  • Input: "123 4-567" becomes "123-45-67" (one block of 3, two blocks of 2)
  • Input: "123 4-5678" becomes "123-456-78" (two blocks of 3, one block of 2)

The solution removes spaces and dashes first, then creates groups of 3 digits. When n % 3 == 1 (1 digit remains), it adjusts the last group to have 2 digits and creates another 2-digit group. When n % 3 == 2 (2 digits remain), it simply adds them as a final group.

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

Intuition

The core insight is that we want to maximize the number of 3-digit blocks while avoiding any 1-digit blocks. Let's think about what happens when we group digits by 3s from left to right.

After grouping as many 3-digit blocks as possible, we can have three scenarios based on the remainder when dividing the total number of digits n by 3:

  1. No remainder (n % 3 == 0): Perfect! All digits fit into 3-digit blocks.

  2. Remainder of 2 (n % 3 == 2): We have 2 digits left after forming all possible 3-digit blocks. These 2 digits can form their own valid block.

  3. Remainder of 1 (n % 3 == 1): This is the tricky case. We can't leave 1 digit alone (violates the constraint). But notice that if we have at least one 3-digit block before this lone digit, we can "borrow" one digit from the last 3-digit block to form two 2-digit blocks instead of having one 3-digit block and one 1-digit block.

For example, if we have "1234", instead of grouping as "123" and "4" (invalid), we regroup as "12" and "34".

This regrouping works because:

  • We transform: [...previous blocks..., ABC, D] into [...previous blocks..., AB, CD]
  • Both resulting blocks have length 2, which is valid
  • We avoid creating any 1-digit blocks

The implementation naturally handles this by:

  1. First creating all 3-digit blocks: ans = [number[i * 3 : i * 3 + 3] for i in range(n // 3)]
  2. If there's 1 digit remaining, modifying the last block to have only 2 digits and creating a new 2-digit block with the last 2 characters
  3. If there are 2 digits remaining, simply appending them as a new block

Solution Approach

The implementation follows a straightforward simulation approach with string manipulation:

  1. Clean the input string: First, we remove all spaces and dashes from the input using replace() method:

    number = number.replace("-", "").replace(" ", "")

    This gives us a string containing only digits.

  2. Calculate initial groupings: We determine how many complete 3-digit blocks we can form:

    n = len(number)
    ans = [number[i * 3 : i * 3 + 3] for i in range(n // 3)]

    This list comprehension creates blocks of 3 digits each. For example, if number = "123456789" and n = 9, we create ["123", "456", "789"].

  3. Handle the remainder cases:

    • When n % 3 == 1: We have 1 digit left over. According to our strategy, we need to redistribute the last 4 digits into two 2-digit blocks:

      ans[-1] = ans[-1][:2]  # Trim the last block to 2 digits
      ans.append(number[-2:]) # Add the last 2 digits as a new block

      For example, if we had ["123", "456", "789", "0"], we modify to get ["123", "456", "78", "90"].

    • When n % 3 == 2: We have 2 digits left over, which forms a valid block by itself:

      ans.append(number[-2:])  # Add the last 2 digits as a new block

      For example, if we had ["123", "456"] with "78" remaining, we get ["123", "456", "78"].

    • When n % 3 == 0: No action needed as all digits are already in 3-digit blocks.

  4. Format the output: Finally, join all blocks with dashes:

    return "-".join(ans)

The time complexity is O(n) where n is the length of the input string, as we process each character a constant number of times. The space complexity is also O(n) for storing the cleaned string and the result list.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the example "123 4-5678" to illustrate the solution approach:

Step 1: Clean the input

  • Input: "123 4-5678"
  • Remove spaces and dashes: "12345678"
  • Length n = 8

Step 2: Create initial 3-digit blocks

  • Calculate how many complete 3-digit blocks: n // 3 = 8 // 3 = 2
  • Create blocks: ["123", "456"]
  • These blocks use characters at positions 0-2 and 3-5

Step 3: Handle remaining digits

  • Calculate remainder: n % 3 = 8 % 3 = 2
  • Since we have 2 digits remaining (positions 6-7: "78"), this forms a valid 2-digit block
  • Append "78" to our list: ["123", "456", "78"]

Step 4: Join with dashes

  • Result: "123-456-78"

Let's trace through another example where redistribution is needed: "123 4-567"

Step 1: Clean the input

  • Input: "123 4-567"
  • Remove spaces and dashes: "1234567"
  • Length n = 7

Step 2: Create initial 3-digit blocks

  • Calculate how many complete 3-digit blocks: n // 3 = 7 // 3 = 2
  • Create blocks: ["123", "456"]
  • These blocks use characters at positions 0-2 and 3-5

Step 3: Handle remaining digits (the tricky case)

  • Calculate remainder: n % 3 = 7 % 3 = 1
  • We have 1 digit remaining (position 6: "7"), which cannot form a valid block alone
  • Solution: Redistribute the last 4 digits ("4567") into two 2-digit blocks
    • Modify the last block from "456" to "45": ans[-1] = "45"
    • Add the last 2 digits as a new block: ans.append("67")
    • Result: ["123", "45", "67"]

Step 4: Join with dashes

  • Result: "123-45-67"

This redistribution ensures we never have a 1-digit block while maximizing the number of 3-digit blocks.

Solution Implementation

1class Solution:
2    def reformatNumber(self, number: str) -> str:
3        # Remove all dashes and spaces from the input string
4        cleaned_number = number.replace("-", "").replace(" ", "")
5      
6        # Get the total length of cleaned digits
7        total_length = len(cleaned_number)
8      
9        # Create groups of 3 digits for as many complete groups as possible
10        groups = []
11        for i in range(total_length // 3):
12            start_index = i * 3
13            end_index = start_index + 3
14            groups.append(cleaned_number[start_index:end_index])
15      
16        # Handle remaining digits based on the remainder when divided by 3
17        remainder = total_length % 3
18      
19        if remainder == 1:
20            # If 1 digit remains, combine it with the last group to make two groups of 2
21            # Remove the last character from the last group (making it 2 digits)
22            groups[-1] = groups[-1][:2]
23            # Add the last 2 digits as a new group
24            groups.append(cleaned_number[-2:])
25        elif remainder == 2:
26            # If 2 digits remain, add them as a final group
27            groups.append(cleaned_number[-2:])
28        # If remainder is 0, all digits are already grouped perfectly
29      
30        # Join all groups with dashes
31        formatted_number = "-".join(groups)
32      
33        return formatted_number
34
1class Solution {
2    public String reformatNumber(String number) {
3        // Remove all dashes and spaces from the input string
4        String cleanedNumber = number.replace("-", "").replace(" ", "");
5        int length = cleanedNumber.length();
6      
7        // List to store formatted groups of digits
8        List<String> formattedGroups = new ArrayList<>();
9      
10        // Create groups of 3 digits for as many complete groups as possible
11        int completeGroups = length / 3;
12        for (int i = 0; i < completeGroups; i++) {
13            int startIndex = i * 3;
14            int endIndex = startIndex + 3;
15            formattedGroups.add(cleanedNumber.substring(startIndex, endIndex));
16        }
17      
18        // Handle remaining digits based on the remainder
19        int remainder = length % 3;
20      
21        if (remainder == 1) {
22            // If 1 digit remains, split the last group of 3 into 2-2
23            // Modify the last group to contain only 2 digits
24            int lastGroupIndex = formattedGroups.size() - 1;
25            String lastGroup = formattedGroups.get(lastGroupIndex);
26            formattedGroups.set(lastGroupIndex, lastGroup.substring(0, 2));
27          
28            // Add the last 2 digits as a new group
29            formattedGroups.add(cleanedNumber.substring(length - 2));
30          
31        } else if (remainder == 2) {
32            // If 2 digits remain, add them as a final group
33            formattedGroups.add(cleanedNumber.substring(length - 2));
34        }
35        // If remainder == 0, no additional processing needed
36      
37        // Join all groups with dashes and return the formatted number
38        return String.join("-", formattedGroups);
39    }
40}
41
1class Solution {
2public:
3    string reformatNumber(string number) {
4        // Step 1: Extract only digits from the input string
5        string digitsOnly;
6        for (char ch : number) {
7            if (ch != ' ' && ch != '-') {
8                digitsOnly.push_back(ch);
9            }
10        }
11      
12        int totalDigits = digitsOnly.size();
13        vector<string> groups;
14      
15        // Step 2: Create groups of 3 digits for as many complete groups as possible
16        int completeGroups = totalDigits / 3;
17        for (int i = 0; i < completeGroups; ++i) {
18            groups.push_back(digitsOnly.substr(i * 3, 3));
19        }
20      
21        // Step 3: Handle remaining digits based on the remainder
22        int remainder = totalDigits % 3;
23      
24        if (remainder == 1) {
25            // If 1 digit remains, combine with last group to form two 2-digit groups
26            // Remove one digit from the last 3-digit group
27            groups.back() = groups.back().substr(0, 2);
28            // Add the last 2 digits as a new group
29            groups.push_back(digitsOnly.substr(totalDigits - 2));
30        } else if (remainder == 2) {
31            // If 2 digits remain, add them as a separate group
32            groups.push_back(digitsOnly.substr(totalDigits - 2));
33        }
34        // If remainder is 0, all digits are already grouped
35      
36        // Step 4: Join all groups with dashes
37        string formattedNumber;
38        for (const auto& group : groups) {
39            formattedNumber += group;
40            formattedNumber += "-";
41        }
42      
43        // Remove the trailing dash
44        formattedNumber.pop_back();
45      
46        return formattedNumber;
47    }
48};
49
1/**
2 * Reformats a phone number string by removing spaces and dashes,
3 * then grouping digits into blocks of 3 (or 2 for the last block if needed)
4 * @param number - The input phone number string containing digits, spaces, and dashes
5 * @returns The reformatted phone number with dashes between blocks
6 */
7function reformatNumber(number: string): string {
8    // Extract only digits from the input string (remove spaces and dashes)
9    const digits: string[] = [...number].filter((char: string) => char !== ' ' && char !== '-');
10  
11    // Store the total number of digits
12    const totalDigits: number = digits.length;
13  
14    // Process each digit and add dashes at appropriate positions
15    const formattedDigits: string[] = digits.map((digit: string, index: number): string => {
16        // Check if we should add a dash after this digit
17        // Case 1: Add dash after every 3rd digit, except for the last 2 or 4 digits
18        const isRegularThirdPosition: boolean = (index + 1) % 3 === 0 && index < totalDigits - 2;
19      
20        // Case 2: Special handling when total digits mod 3 equals 1
21        // We need to make the last two blocks have 2 digits each
22        const isSpecialPosition: boolean = totalDigits % 3 === 1 && totalDigits - 3 === index;
23      
24        if (isRegularThirdPosition || isSpecialPosition) {
25            return digit + '-';
26        }
27      
28        return digit;
29    });
30  
31    // Join all characters to form the final formatted string
32    return formattedDigits.join('');
33}
34

Time and Space Complexity

The time complexity is O(n), where n is the length of the input string number.

  • The replace() operations iterate through the string twice, taking O(n) time
  • Computing the length takes O(1) time
  • The list comprehension creates approximately n/3 groups, each slice operation takes O(1) time, resulting in O(n/3) = O(n) time total
  • The conditional modifications and append operations take O(1) time
  • The join() operation iterates through all characters to build the final string, taking O(n) time

Overall, the operations are sequential, resulting in O(n) + O(n) + O(n) = O(n) time complexity.

The space complexity is O(n), where n is the length of the input string number.

  • The cleaned number string after replacements stores up to n characters, using O(n) space
  • The ans list stores groups of the digits, which collectively contain all digits from the cleaned string, using O(n) space
  • The final joined string also uses O(n) space (including the added dashes)

The total space usage is proportional to the input size, resulting in O(n) space complexity.

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

Common Pitfalls

1. Index Out of Range When Modifying the Last Group

The Pitfall: When n % 3 == 1, the code attempts to modify the last group by trimming it (groups[-1] = groups[-1][:2]). However, if the total number of digits is exactly 1, the groups list will be empty since 1 // 3 = 0, causing an IndexError when trying to access groups[-1].

Example that breaks:

number = "1"  # After cleaning: "1"
# groups = [] (empty list because 1 // 3 = 0)
# groups[-1] would raise IndexError

Solution: Add a special case check for when the total length is less than or equal to 3:

def reformatNumber(self, number: str) -> str:
    cleaned_number = number.replace("-", "").replace(" ", "")
    total_length = len(cleaned_number)
  
    # Handle edge cases for very short numbers
    if total_length <= 3:
        return cleaned_number
  
    # Continue with the regular logic...

2. Incorrect Slicing When Remainder is 1

The Pitfall: When calculating the last 2 digits with cleaned_number[-2:], this assumes we're taking the last 2 digits of the entire string. But conceptually, we should be taking one digit from the last group (making it 2 digits) and the remaining digit. The current code works but can be confusing and error-prone if modified.

More Clear Approach:

if remainder == 1:
    # We have processed (total_length // 3) groups of 3
    # The last group has 3 digits, and we have 1 extra digit
    # We need to redistribute these 4 digits into two groups of 2
    groups[-1] = groups[-1][:2]  # Make the last group have only 2 digits
    groups.append(cleaned_number[-2:])  # Add the last 2 digits

3. Not Handling Empty Input

The Pitfall: If the input contains only spaces and dashes with no digits, the cleaned string becomes empty, and attempting to form groups or join them could lead to unexpected behavior.

Example:

number = "-- - -"  # After cleaning: ""
# This would return an empty string, which might not be the intended behavior

Solution: Add validation for empty cleaned strings:

def reformatNumber(self, number: str) -> str:
    cleaned_number = number.replace("-", "").replace(" ", "")
  
    if not cleaned_number:
        return ""  # or raise an exception based on requirements
  
    # Continue with the regular logic...

Complete Robust Solution:

class Solution:
    def reformatNumber(self, number: str) -> str:
        # Remove all dashes and spaces
        cleaned_number = number.replace("-", "").replace(" ", "")
      
        # Handle edge cases
        if not cleaned_number:
            return ""
      
        total_length = len(cleaned_number)
      
        # Handle short numbers (3 or fewer digits)
        if total_length <= 3:
            return cleaned_number
      
        # Create groups of 3 digits
        groups = []
        num_full_groups = total_length // 3
      
        for i in range(num_full_groups):
            groups.append(cleaned_number[i * 3 : i * 3 + 3])
      
        # Handle remaining digits
        remainder = total_length % 3
      
        if remainder == 1:
            # Redistribute last 4 digits into two groups of 2
            groups[-1] = groups[-1][:2]
            groups.append(cleaned_number[-2:])
        elif remainder == 2:
            # Add the last 2 digits as a final group
            groups.append(cleaned_number[-2:])
      
        return "-".join(groups)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

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

Load More