Reformat Phone Number: String Grouping Pattern
You are given a phone number as a string that contains digits, spaces, and dashes. Your task is to reformat it by first removing all spaces and dashes, then grouping the digits. Groups of 3 are preferred, but the last block must never be a single digit. If 4 digits remain at the end, split them into two groups of 2. Finally, join the groups with dashes.
This is LeetCode 1694: Reformat Phone Number, an easy problem that tests your ability to process strings methodically. The grouping logic is simple once you see the pattern, but getting the edge cases right requires careful handling of the tail end of the digit string.
Why this problem matters
Reformat Phone Number is a practical string manipulation problem. In real-world applications, you constantly normalize user input: phone numbers, credit cards, serial numbers. The core skill is stripping unwanted characters and then chunking what remains into fixed-size blocks with special rules for the last chunk.
This same grouping pattern appears whenever you need to partition a sequence into fixed-size pieces with a constraint on the final piece. Learning to handle the "leftover" digits cleanly is the real lesson here.
The key insight
After stripping non-digit characters, you have a clean string of digits. From here, the algorithm is:
- Take groups of 3 from the left as long as 4 or more digits remain.
- When you are down to 2 or 3 remaining digits, they form the last group as-is.
- When you are down to 4 remaining digits, split them into two groups of 2 (because a group of 3 + a group of 1 is not allowed).
The critical rule is: never leave a single digit by itself. That is the only tricky part. Everything else is a simple loop.
The solution
def reformatNumber(number):
digits = number.replace(" ", "").replace("-", "")
groups = []
i = 0
while len(digits) - i > 4:
groups.append(digits[i:i+3])
i += 3
remaining = len(digits) - i
if remaining == 4:
groups.append(digits[i:i+2])
groups.append(digits[i+2:i+4])
else:
groups.append(digits[i:])
return "-".join(groups)
Here is how each part works:
digits = number.replace(" ", "").replace("-", "")strips all spaces and dashes, leaving only digits.- The
whileloop takes groups of 3 as long as more than 4 digits remain. The conditionlen(digits) - i > 4ensures we stop before we accidentally leave just 1 digit. - After the loop,
remainingis 2, 3, or 4. If it is 4, we split into two groups of 2. Otherwise, we take the remaining 2 or 3 digits as a single group. "-".join(groups)produces the final formatted string.
The condition > 4 (not >= 4) is the key. When exactly 4 digits remain, you do not take a group of 3 (which would leave 1). Instead you split into 2 + 2.
Visual walkthrough
Step 1: Strip all non-digit characters from the input.
Remove spaces and dashes. Only keep characters 0-9. Input "1-23 456" becomes "123456".
Step 2: Group digits into blocks of 3 from the left.
Take groups of 3 as long as 4 or more digits remain. After taking three groups of 3, one digit remains.
Step 3: Handle the remaining digits. If 2 or 3 remain, they form one group. If 4 remain, split into two groups of 2.
You never leave just 1 digit. If 1 remains, pop the last group of 3 and redistribute those 4 digits as two groups of 2: "789" + "0" becomes "78" + "90".
Step 4: Join all groups with dashes to produce the final result.
Join with "-". Final answer: "123-456-78-90".
The walkthrough uses the input "1-23 456 78-9 0" which has 10 digits after stripping. The loop takes three groups of 3, leaving 1 digit. Since 1 alone is not valid, we back up: the last group of 3 and the leftover digit (4 digits total) get split into two groups of 2. The result is "123-456-78-90".
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Linear scan | O(n) | O(n) |
The time is O(n) because you scan through the string once to strip characters and once more to form groups. The space is O(n) for storing the cleaned digit string and the output groups. There is no way to avoid O(n) space since the problem asks you to return a new string.
The building blocks
1. Character filtering
digits = number.replace(" ", "").replace("-", "")
Stripping unwanted characters from a string is one of the most common string operations. You can also use a list comprehension with isdigit(), but chained replace calls are cleaner when you know exactly which characters to remove.
2. Fixed-size chunking with tail handling
while len(data) - i > threshold:
groups.append(data[i:i+chunk_size])
i += chunk_size
This pattern appears whenever you need to split a sequence into fixed-size blocks. The trick is choosing the right stopping condition so the leftover is always a valid size. In this problem, the threshold is 4 and the chunk size is 3. In other problems (like Base64 encoding or packet framing), the numbers change but the structure is identical.
Edge cases
Before submitting, make sure your solution handles these:
- Exactly 2 digits
"1 2": after stripping,"12". One group of 2. Returns"12". - Exactly 3 digits
"1-2-3": after stripping,"123". One group of 3. Returns"123". - Exactly 4 digits
"1 2 3 4": after stripping,"1234". Two groups of 2. Returns"12-34". - Long string with no spaces or dashes
"123456789": 9 digits, three groups of 3. Returns"123-456-789". - All spaces and dashes between single digits
"1 - 2 - 3 - 4 - 5": stripping produces"12345". Group as"123-45". - Trailing spaces
"12 --3 ": stripping produces"123". Returns"123".
The algorithm handles all of these without special-case logic because the while loop and the remaining-digit check cover every scenario.
From understanding to recall
You have read the solution and it makes sense. Strip, loop, handle the tail, join. Four steps, no tricks. But can you write it from scratch without looking?
The details matter: using > 4 instead of > 3 as the loop condition, remembering to split 4 remaining digits into 2 + 2, and joining with dashes at the end. These are small decisions that are easy to second-guess under interview pressure.
Spaced repetition closes that gap. You practice writing the grouping loop from scratch at increasing intervals. After a few rounds, the pattern is automatic. You see "chunk a string into groups with a tail constraint" and the code flows out without hesitation.
The fixed-size chunking pattern is one of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Learning them individually and drilling them with spaced repetition is far more effective than grinding random problems and hoping they stick.
Related posts
- Add Strings - Character-by-character string building with carry logic
- Group Anagrams - Grouping strings by a computed key
- Length of Last Word - Simple string scanning with edge case handling
CodeBricks breaks the reformat phone number problem into its character filtering and fixed-size chunking building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a string grouping question shows up in your interview, you do not think about it. You just write it.