1417. Reformat The String
Problem Description
You have a string s
that contains only lowercase English letters and digits (alphanumeric characters).
Your task is to rearrange the characters in the string so that no two adjacent characters are of the same type. Specifically:
- No letter should be followed by another letter
- No digit should be followed by another digit
The characters must alternate between letters and digits throughout the entire string.
If it's possible to create such an arrangement, return the reformatted string. If it's impossible to achieve this alternating pattern, return an empty string ""
.
For example:
- If
s = "a0b1c2"
, a valid output could be"a0b1c2"
(already alternating) or"0a1b2c"
- If
s = "leetcode"
, the output would be""
because there are no digits to alternate with the letters - If
s = "1229857369"
, the output would be""
because there are no letters to alternate with the digits
The key insight is that for alternation to be possible, the absolute difference between the count of letters and the count of digits cannot exceed 1. If we have too many of one type compared to the other, we won't be able to alternate them properly.
Intuition
To create an alternating pattern between letters and digits, we need to think about when such a pattern is possible. Consider what happens when we try to alternate two types of characters:
If we have 5 letters and 2 digits, we'd try to arrange them like: L D L D L ? ?
where L represents a letter and D represents a digit. We have 3 extra letters with nowhere to go without breaking the alternating rule.
This observation leads to a critical insight: the difference between the count of letters and digits cannot be more than 1. Why? Because in an alternating sequence, the two types must be nearly equal in quantity. At most, one type can have exactly one more character than the other (which would go at the end).
Once we know alternation is possible, how do we build the result? We can use a zipper-like approach:
- Separate all letters into one group and all digits into another
- The group with more characters should go first (to ensure we can place all characters)
- Interleave the characters from both groups, taking one from each alternately
- If one group has an extra character, append it at the end
For example, with 3 letters [a, b, c]
and 2 digits [1, 2]
:
- Start with the larger group (letters)
- Zip them together:
a-1
,b-2
,c
- Result:
"a1b2c"
This zipper approach naturally creates the alternating pattern we need, and by starting with the larger group, we ensure the extra character (if any) ends up at the correct position.
Solution Approach
The implementation follows a straightforward approach based on our intuition:
Step 1: Separate characters by type
a = [c for c in s if c.islower()] b = [c for c in s if c.isdigit()]
We use list comprehensions to create two separate lists:
- List
a
contains all lowercase letters - List
b
contains all digits
Step 2: Check if alternation is possible
if abs(len(a) - len(b)) > 1:
return ''
If the absolute difference between the counts exceeds 1, it's impossible to create an alternating pattern, so we return an empty string immediately.
Step 3: Ensure the longer list comes first
if len(a) < len(b):
a, b = b, a
We swap the lists if necessary so that a
always refers to the list with more (or equal) elements. This ensures that when we interleave, any extra character will be from list a
and can be placed at the end.
Step 4: Build the alternating string
ans = []
for x, y in zip(a, b):
ans.append(x + y)
We use Python's zip()
function to pair up elements from both lists. Each iteration takes one character from a
and one from b
, concatenates them, and adds to our result list. The zip()
function stops when the shorter list is exhausted.
Step 5: Handle the extra character (if any)
if len(a) > len(b):
ans.append(a[-1])
If list a
has one more element than b
, the last element of a
wasn't paired by zip()
. We append it to complete our alternating sequence.
Step 6: Return the final string
return ''.join(ans)
Finally, we join all the parts together to form the complete alternating string.
The time complexity is O(n)
where n
is the length of the input string, as we iterate through the string once to separate characters and once more to build the result. The space complexity is also O(n)
for storing the separated lists and the result.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "ab123c"
.
Step 1: Separate characters by type
- Letters:
a = ['a', 'b', 'c']
(3 letters) - Digits:
b = ['1', '2', '3']
(3 digits)
Step 2: Check if alternation is possible
abs(3 - 3) = 0
, which is ≤ 1 ✓- Alternation is possible!
Step 3: Ensure the longer list comes first
- Both lists have equal length (3 = 3)
- No swap needed,
a
remains letters,b
remains digits
Step 4: Build the alternating string using zip
zip(a, b)
pairs:('a','1')
,('b','2')
,('c','3')
- After concatenating each pair:
ans = ['a1', 'b2', 'c3']
Step 5: Handle extra character
len(a) = len(b)
, so no extra character to append
Step 6: Join and return
''.join(ans) = "a1b2c3"
- Final result:
"a1b2c3"
Let's trace through another example where counts differ: s = "covid2019"
Step 1: Separate characters
- Letters:
a = ['c', 'o', 'v', 'i', 'd']
(5 letters) - Digits:
b = ['2', '0', '1', '9']
(4 digits)
Step 2: Check if alternation is possible
abs(5 - 4) = 1
, which is ≤ 1 ✓
Step 3: Ensure longer list comes first
a
already has more elements (5 > 4), no swap needed
Step 4: Build with zip
zip(a, b)
pairs:('c','2')
,('o','0')
,('v','1')
,('i','9')
- Note: 'd' from list
a
is unpaired ans = ['c2', 'o0', 'v1', 'i9']
Step 5: Handle extra character
- Since
len(a) > len(b)
, appenda[-1]
which is'd'
ans = ['c2', 'o0', 'v1', 'i9', 'd']
Step 6: Join and return
- Result:
"c2o0v1i9d"
Solution Implementation
1class Solution:
2 def reformat(self, s: str) -> str:
3 # Separate letters and digits into two lists
4 letters = [char for char in s if char.isalpha()]
5 digits = [char for char in s if char.isdigit()]
6
7 # Check if valid alternating pattern is possible
8 # The difference in lengths cannot exceed 1
9 if abs(len(letters) - len(digits)) > 1:
10 return ''
11
12 # Ensure the longer list is first for proper alternation
13 # This simplifies the alternating logic
14 if len(letters) < len(digits):
15 letters, digits = digits, letters
16
17 # Build the result by alternating between the two lists
18 result = []
19 for first_char, second_char in zip(letters, digits):
20 result.append(first_char + second_char)
21
22 # If one list is longer, append the remaining character
23 if len(letters) > len(digits):
24 result.append(letters[-1])
25
26 # Join all parts into the final string
27 return ''.join(result)
28
1class Solution {
2 public String reformat(String s) {
3 // Separate digits and letters into two StringBuilders
4 StringBuilder digits = new StringBuilder();
5 StringBuilder letters = new StringBuilder();
6
7 // Iterate through each character and categorize them
8 for (char c : s.toCharArray()) {
9 if (Character.isDigit(c)) {
10 digits.append(c);
11 } else {
12 letters.append(c);
13 }
14 }
15
16 // Get the lengths of digits and letters
17 int digitCount = digits.length();
18 int letterCount = letters.length();
19
20 // If the difference in counts is more than 1, reformatting is impossible
21 if (Math.abs(digitCount - letterCount) > 1) {
22 return "";
23 }
24
25 // Build the result string by alternating between digits and letters
26 StringBuilder result = new StringBuilder();
27
28 // Alternate characters from both groups
29 // The group with more characters goes first
30 for (int i = 0; i < Math.min(digitCount, letterCount); i++) {
31 if (digitCount > letterCount) {
32 // Start with digit if there are more digits
33 result.append(digits.charAt(i));
34 result.append(letters.charAt(i));
35 } else {
36 // Start with letter if there are more letters (or equal)
37 result.append(letters.charAt(i));
38 result.append(digits.charAt(i));
39 }
40 }
41
42 // Append the remaining character if one group has an extra character
43 if (digitCount > letterCount) {
44 result.append(digits.charAt(digitCount - 1));
45 }
46 if (digitCount < letterCount) {
47 result.append(letters.charAt(letterCount - 1));
48 }
49
50 return result.toString();
51 }
52}
53
1class Solution {
2public:
3 string reformat(string s) {
4 // Separate digits and letters into two different strings
5 string digits = "";
6 string letters = "";
7
8 // Iterate through input string and categorize each character
9 for (char c : s) {
10 if (isdigit(c)) {
11 digits += c;
12 } else {
13 letters += c;
14 }
15 }
16
17 // Get the sizes of both strings
18 int digitCount = digits.size();
19 int letterCount = letters.size();
20
21 // If the difference in counts is more than 1, reformatting is impossible
22 if (abs(digitCount - letterCount) > 1) {
23 return "";
24 }
25
26 // Build the result string by alternating between digits and letters
27 string result = "";
28
29 // Alternate characters from both strings
30 // The string with more characters goes first
31 for (int i = 0; i < min(digitCount, letterCount); ++i) {
32 if (digitCount > letterCount) {
33 // Start with digit if there are more digits
34 result += digits[i];
35 result += letters[i];
36 } else {
37 // Start with letter if there are more letters (or equal)
38 result += letters[i];
39 result += digits[i];
40 }
41 }
42
43 // Append the remaining character from the longer string (if any)
44 if (digitCount > letterCount) {
45 result += digits[digitCount - 1];
46 }
47 if (letterCount > digitCount) {
48 result += letters[letterCount - 1];
49 }
50
51 return result;
52 }
53};
54
1function reformat(s: string): string {
2 // Separate digits and letters into two different arrays
3 const digits: string[] = [];
4 const letters: string[] = [];
5
6 // Iterate through input string and categorize each character
7 for (const char of s) {
8 if (char >= '0' && char <= '9') {
9 digits.push(char);
10 } else {
11 letters.push(char);
12 }
13 }
14
15 // Get the counts of digits and letters
16 const digitCount = digits.length;
17 const letterCount = letters.length;
18
19 // If the difference in counts is more than 1, reformatting is impossible
20 if (Math.abs(digitCount - letterCount) > 1) {
21 return "";
22 }
23
24 // Build the result string by alternating between digits and letters
25 let result = "";
26
27 // Alternate characters from both arrays
28 // The array with more elements goes first
29 const minCount = Math.min(digitCount, letterCount);
30
31 for (let i = 0; i < minCount; i++) {
32 if (digitCount > letterCount) {
33 // Start with digit if there are more digits
34 result += digits[i];
35 result += letters[i];
36 } else {
37 // Start with letter if there are more letters (or equal)
38 result += letters[i];
39 result += digits[i];
40 }
41 }
42
43 // Append the remaining character from the longer array (if any)
44 if (digitCount > letterCount) {
45 result += digits[digitCount - 1];
46 } else if (letterCount > digitCount) {
47 result += letters[letterCount - 1];
48 }
49
50 return result;
51}
52
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs the following operations:
- Two list comprehensions to separate letters and digits, each iterating through the string once:
O(n)
- Computing the length difference and comparison:
O(1)
- Swapping references if needed:
O(1)
- The
zip
operation iterates through the shorter list:O(n/2)
in the worst case - Building the answer list with append operations:
O(n)
- The final
join
operation:O(n)
All operations are linear, so the overall time complexity is O(n)
, where n
is the length of the input string.
Space Complexity: O(n)
The algorithm uses the following additional space:
- List
a
stores all letters: up toO(n)
space - List
b
stores all digits: up toO(n)
space - List
ans
stores the result:O(n)
space - The final string created by
join
:O(n)
space
Since the lists a
and b
together contain all characters from the input string, they use O(n)
space combined. The answer list and final string also use O(n)
space. Therefore, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling the Order of Alternation Correctly
The Pitfall: A common mistake is assuming that the output must always start with a letter or always start with a digit. The current solution always puts the character from the longer list first, which means if there are more digits than letters, the output will start with a digit. Some developers might incorrectly assume the output must follow a specific pattern like "letter-digit-letter-digit..."
Example of the Issue:
s = "covid2019" # 5 letters, 4 digits # Current solution might return: "c2o0v1i9d" # But "2c0o1v9i4d" would also be valid
Solution: The current approach is actually correct - it doesn't matter which type starts the sequence as long as they alternate. However, if you need a specific starting type, you could modify the code:
# If you want letters to always come first when possible:
if len(digits) > len(letters):
# Only swap if digits actually outnumber letters
letters, digits = digits, letters
start_with_digit = True
else:
start_with_digit = False
2. Forgetting Edge Cases with Equal Counts
The Pitfall: When the number of letters equals the number of digits, developers might overthink the logic and add unnecessary complexity, not realizing that the existing code handles this case perfectly.
Example:
s = "ab12" # 2 letters, 2 digits # The code correctly handles this without special casing
Solution:
The current implementation already handles this correctly by using zip()
which pairs elements perfectly when lists are equal length, and the final check if len(letters) > len(digits)
won't execute, leaving the result complete.
3. Inefficient String Concatenation in the Loop
The Pitfall: While the current solution uses a list and joins at the end (which is efficient), a common mistake would be to use string concatenation directly:
# Inefficient approach (don't do this):
result = ""
for first_char, second_char in zip(letters, digits):
result += first_char + second_char # String concatenation in loop
Why it's a Problem: Strings are immutable in Python, so each concatenation creates a new string object, leading to O(n²) time complexity in the worst case.
Solution: Always use a list to collect string parts and join them at the end, as the current solution correctly does:
result = []
for first_char, second_char in zip(letters, digits):
result.append(first_char + second_char)
return ''.join(result)
4. Not Preserving the Original Character Order
The Pitfall: Some might think they need to sort or rearrange the letters and digits within their respective groups. However, the problem only requires alternation between types, not any specific ordering within each type.
Example:
s = "cab24" # Correct: "c2a4b" (preserves order: c->a->b and 2->4) # Incorrect assumption: thinking you need "a2b4c" (alphabetically sorted)
Solution: The current implementation correctly preserves the original order of characters within each type by using list comprehensions that maintain the sequence from the original string.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
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!