Facebook Pixel

1417. Reformat The String

EasyString
Leetcode Link

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.

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

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:

  1. Separate all letters into one group and all digits into another
  2. The group with more characters should go first (to ensure we can place all characters)
  3. Interleave the characters from both groups, taking one from each alternately
  4. 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 Evaluator

Example 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), append a[-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 to O(n) space
  • List b stores all digits: up to O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More