1694. Reformat Phone Number
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:
-
Remove all spaces and dashes from the input string, keeping only the digits.
-
Group the digits from left to right into blocks of length 3, continuing until you have 4 or fewer digits remaining.
-
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
-
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.
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:
-
No remainder (
n % 3 == 0
): Perfect! All digits fit into 3-digit blocks. -
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. -
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:
- First creating all 3-digit blocks:
ans = [number[i * 3 : i * 3 + 3] for i in range(n // 3)]
- 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
- 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:
-
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.
-
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"
andn = 9
, we create["123", "456", "789"]
. -
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.
-
-
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 EvaluatorExample 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"]
- Modify the last block from "456" to "45":
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, takingO(n)
time - Computing the length takes
O(1)
time - The list comprehension creates approximately
n/3
groups, each slice operation takesO(1)
time, resulting inO(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, takingO(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 ton
characters, usingO(n)
space - The
ans
list stores groups of the digits, which collectively contain all digits from the cleaned string, usingO(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)
Which of the following is a good use case for backtracking?
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!