Facebook Pixel

761. Special Binary String

Problem Description

You're given a special type of binary string that has two key properties:

  1. It contains an equal number of 0s and 1s
  2. At any point while reading the string from left to right, you'll always have seen at least as many 1s as 0s (every prefix has more or equal 1s than 0s)

For example, "1100" is special because:

  • It has 2 ones and 2 zeros (equal count)
  • Reading left to right: "1" has 1 one, 0 zeros βœ“; "11" has 2 ones, 0 zeros βœ“; "110" has 2 ones, 1 zero βœ“; "1100" has 2 ones, 2 zeros βœ“

Your task is to rearrange the given special binary string s to make it lexicographically largest possible. You can do this through a series of moves where each move involves:

  • Finding two special substrings that are next to each other (consecutive)
  • Swapping their positions

Two substrings are consecutive if they're adjacent in the string with no gap between them. Both substrings being swapped must themselves be special (following the same two properties).

The goal is to return the largest possible string in lexicographical order after performing any number of these swapping operations. In lexicographical order for binary strings, strings starting with 1 come after strings starting with 0, and longer sequences of 1s at the beginning are considered larger.

For instance, if you have a string like "11011000", you might be able to rearrange its special substrings to get something like "11100100" which would be lexicographically larger.

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

Intuition

The key insight is recognizing that special binary strings have a recursive structure similar to balanced parentheses. We can think of 1 as an opening parenthesis and 0 as a closing parenthesis.

Every special binary string can be viewed as either:

  1. A concatenation of multiple special binary strings, or
  2. A string of the form "1" + inner + "0" where inner is itself a (possibly empty) special binary string

This recursive structure suggests we can decompose the problem. When we encounter a complete special substring (where the count of 1s equals the count of 0s), we've found a "unit" that can be treated independently.

To maximize the lexicographical value, we need to arrange these independent special substrings in descending order. Why? Because in binary, having 1s appear earlier makes the string larger. If we have special substrings like "1100" and "111000", placing "111000" first gives us "1110001100" which is larger than "1100111000".

The algorithm tracks a running count: add 1 for each '1' and subtract 1 for each '0'. When this count returns to 0, we've identified a complete special substring. For each such substring, we recursively process its interior (the part between the first 1 and last 0) to maximize it, then wrap it back with "1" and "0".

After finding all special substrings at the current level, we sort them in descending order to get the lexicographically largest arrangement. This greedy approach works because special substrings are independent units that can be freely rearranged without breaking the special property of the overall string.

The beauty of this solution is that it naturally handles nested structures - inner special substrings are optimized recursively before the outer level arranges them optimally.

Learn more about Recursion patterns.

Solution Approach

The implementation uses a recursive approach with string manipulation and sorting to solve the problem.

Algorithm Steps:

  1. Base Case: If the string is empty, return an empty string.

  2. Initialize Variables:

    • ans - list to store all special substrings found at the current level
    • cnt - counter to track the balance between 1s and 0s
    • i - current position pointer
    • j - start position of the current special substring
  3. Iterate Through the String:

    • For each character at position i:
      • If it's '1', increment cnt by 1
      • If it's '0', decrement cnt by 1
    • When cnt becomes 0, we've found a complete special substring from position j to i
  4. Process Each Special Substring:

    • Extract the inner part: s[j+1 : i] (excluding the outer 1 and 0)
    • Recursively call makeLargestSpecial on the inner part to maximize it
    • Reconstruct the special substring: '1' + maximized_inner + '0'
    • Add this to the ans list
    • Update j = i + 1 to start looking for the next special substring
  5. Sort and Combine:

    • Sort all special substrings in ans in descending order (reverse=True)
    • Join them together to form the final result

Example Walkthrough:

For input s = "11011000":

  • First iteration finds "1100" when cnt reaches 0 at index 3
    • Inner part is "10", recursively processed
    • Reconstructed as "1" + "10" + "0" = "1100"
  • Second iteration finds "110000" when cnt reaches 0 at index 7
    • Inner part is "1000", recursively processed
    • Reconstructed as "1" + "1000" + "0" = "110000"
  • Sort ["1100", "110000"] in descending order β†’ ["110000", "1100"]
  • Final result: "1100001100"

The time complexity is O(nΒ²) in the worst case due to recursive calls and sorting, where n is the length of the string. The space complexity is O(n) for the recursion stack and temporary storage.

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 solution with the input string s = "11011000".

Step 1: Initial Setup

  • Start with cnt = 0, j = 0, ans = []
  • We'll track the balance of 1s and 0s as we traverse

Step 2: First Pass - Finding Special Substrings

Position 0: '1' β†’ cnt = 1 Position 1: '1' β†’ cnt = 2
Position 2: '0' β†’ cnt = 1 Position 3: '0' β†’ cnt = 0 βœ“ Found special substring!

  • Extract substring from j=0 to i=3: "1100"
  • Inner part (excluding outer 1 and 0): "10"
  • Recursively process "10":
    • "10" itself is already optimal (single 1-0 pair)
    • Returns "10"
  • Reconstruct: "1" + "10" + "0" = "1100"
  • Add to ans = ["1100"]
  • Update j = 4

Continue from position 4: Position 4: '1' β†’ cnt = 1 Position 5: '1' β†’ cnt = 2 Position 6: '0' β†’ cnt = 1 Position 7: '0' β†’ cnt = 0 βœ“ Found another special substring!

  • Extract substring from j=4 to i=7: "1100"
  • Inner part: "10"
  • Recursively process: returns "10"
  • Reconstruct: "1" + "10" + "0" = "1100"
  • Add to ans = ["1100", "1100"]

Step 3: Sort and Combine

  • Sort ["1100", "1100"] in descending order
  • Both are equal, so order doesn't change
  • Join together: "11001100"

Final Result: "11001100"

Note: This example shows how the algorithm identifies special substrings when the count returns to 0, recursively optimizes their inner parts, and then arranges them in descending order to achieve the lexicographically largest result.

Solution Implementation

1class Solution:
2    def makeLargestSpecial(self, s: str) -> str:
3        """
4        Rearranges a special binary string to get the lexicographically largest result.
5        A special string starts with '1', ends with '0', and has equal number of '1's and '0's
6        with every prefix having at least as many '1's as '0's.
7        """
8        # Base case: empty string returns empty
9        if not s:
10            return ''
11      
12        # List to store special substrings found at the current level
13        substrings = []
14      
15        # Counter to track balance of '1's and '0's
16        balance = 0
17      
18        # Start index of current special substring
19        start = 0
20      
21        # Iterate through the string to find special substrings
22        for i in range(len(s)):
23            # Increment balance for '1', decrement for '0'
24            balance += 1 if s[i] == '1' else -1
25          
26            # When balance returns to 0, we've found a complete special substring
27            if balance == 0:
28                # Recursively process the inner content (excluding first '1' and last '0')
29                # Then wrap it with '1' at start and '0' at end
30                inner_content = self.makeLargestSpecial(s[start + 1:i])
31                special_substring = '1' + inner_content + '0'
32                substrings.append(special_substring)
33              
34                # Move start pointer to begin searching for next special substring
35                start = i + 1
36      
37        # Sort all special substrings in descending order (lexicographically largest first)
38        substrings.sort(reverse=True)
39      
40        # Concatenate all sorted substrings to form the final result
41        return ''.join(substrings)
42
1class Solution {
2    /**
3     * Makes the lexicographically largest special binary string by recursively
4     * rearranging valid special substrings.
5     * 
6     * A special binary string is defined as:
7     * - It has equal number of 0s and 1s
8     * - Every prefix has at least as many 1s as 0s
9     * 
10     * @param s the input binary string
11     * @return the lexicographically largest special string after rearrangement
12     */
13    public String makeLargestSpecial(String s) {
14        // Base case: empty string returns empty
15        if (s.isEmpty()) {
16            return "";
17        }
18      
19        // List to store all special substrings at current level
20        List<String> specialSubstrings = new ArrayList<>();
21      
22        // Counter to track balance of 1s and 0s
23        int balance = 0;
24      
25        // Start index of current special substring
26        int startIndex = 0;
27      
28        // Iterate through the string to find special substrings
29        for (int currentIndex = 0; currentIndex < s.length(); currentIndex++) {
30            // Increment balance for '1', decrement for '0'
31            if (s.charAt(currentIndex) == '1') {
32                balance++;
33            } else {
34                balance--;
35            }
36          
37            // When balance returns to 0, we found a complete special substring
38            if (balance == 0) {
39                // Recursively process the inner content (excluding outer 1 and 0)
40                String innerContent = makeLargestSpecial(
41                    s.substring(startIndex + 1, currentIndex)
42                );
43              
44                // Reconstruct the special substring with processed inner content
45                String specialString = "1" + innerContent + "0";
46                specialSubstrings.add(specialString);
47              
48                // Move start index to begin next special substring search
49                startIndex = currentIndex + 1;
50            }
51        }
52      
53        // Sort all special substrings in descending order (lexicographically largest first)
54        specialSubstrings.sort(Comparator.reverseOrder());
55      
56        // Concatenate all sorted special substrings to form the result
57        return String.join("", specialSubstrings);
58    }
59}
60
1class Solution {
2public:
3    string makeLargestSpecial(string s) {
4        // Base case: empty string returns itself
5        if (s.empty()) {
6            return s;
7        }
8      
9        // Store all special substrings at the current level
10        vector<string> specialSubstrings;
11      
12        // Track balance of 1s and 0s (1 adds +1, 0 adds -1)
13        int balance = 0;
14      
15        // Start position of current special substring
16        int startPos = 0;
17      
18        // Iterate through the string to find special substrings
19        for (int currentPos = 0; currentPos < s.size(); ++currentPos) {
20            // Update balance: increment for '1', decrement for '0'
21            balance += (s[currentPos] == '1') ? 1 : -1;
22          
23            // When balance returns to 0, we found a complete special substring
24            if (balance == 0) {
25                // Recursively process the inner part (excluding outer 1 and 0)
26                // and wrap it with "1" prefix and "0" suffix
27                string innerPart = s.substr(startPos + 1, currentPos - startPos - 1);
28                string processedSpecial = "1" + makeLargestSpecial(innerPart) + "0";
29                specialSubstrings.push_back(processedSpecial);
30              
31                // Move start position to the next character
32                startPos = currentPos + 1;
33            }
34        }
35      
36        // Sort special substrings in descending order (lexicographically largest first)
37        sort(specialSubstrings.begin(), specialSubstrings.end(), greater<string>());
38      
39        // Concatenate all sorted special substrings to form the result
40        string result = "";
41        for (const string& substring : specialSubstrings) {
42            result += substring;
43        }
44      
45        return result;
46    }
47};
48
1function makeLargestSpecial(s: string): string {
2    // Base case: empty string returns itself
3    if (s.length === 0) {
4        return s;
5    }
6  
7    // Store all special substrings at the current level
8    const specialSubstrings: string[] = [];
9  
10    // Track balance of 1s and 0s (1 adds +1, 0 adds -1)
11    let balance = 0;
12  
13    // Start position of current special substring
14    let startPos = 0;
15  
16    // Iterate through the string to find special substrings
17    for (let currentPos = 0; currentPos < s.length; currentPos++) {
18        // Update balance: increment for '1', decrement for '0'
19        balance += s[currentPos] === '1' ? 1 : -1;
20      
21        // When balance returns to 0, we found a complete special substring
22        if (balance === 0) {
23            // Recursively process the inner part (excluding outer 1 and 0)
24            // and wrap it with "1" prefix and "0" suffix
25            const innerPart = s.substring(startPos + 1, currentPos);
26            const processedSpecial = "1" + makeLargestSpecial(innerPart) + "0";
27            specialSubstrings.push(processedSpecial);
28          
29            // Move start position to the next character
30            startPos = currentPos + 1;
31        }
32    }
33  
34    // Sort special substrings in descending order (lexicographically largest first)
35    specialSubstrings.sort((a, b) => b.localeCompare(a));
36  
37    // Concatenate all sorted special substrings to form the result
38    let result = "";
39    for (const substring of specialSubstrings) {
40        result += substring;
41    }
42  
43    return result;
44}
45

Time and Space Complexity

Time Complexity: O(n^2) where n is the length of the input string.

The analysis breaks down as follows:

  • The main function traverses the string once with the while loop: O(n)
  • For each balanced substring found (when cnt == 0), we make a recursive call on the substring between the first '1' and last '0'
  • In the worst case, the recursion depth can be O(n) (for deeply nested special strings like "111...000...")
  • At each recursion level, we perform string slicing s[j+1:i] which takes O(n) time
  • The sorting operation ans.sort(reverse=True) takes O(k log k) where k is the number of substrings at that level, but k ≀ n
  • Overall, considering the recursive calls and string operations at each level: O(n) * O(n) = O(n^2)

Space Complexity: O(n^2)

The space analysis includes:

  • Recursion call stack depth: O(n) in the worst case
  • At each recursion level, we create new substrings through slicing and store them in the ans list
  • String slicing creates new strings, and the total space for all substrings across all recursive calls can be O(n^2) in the worst case
  • The accumulated space for storing intermediate results and substrings: O(n^2)

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

Common Pitfalls

1. Misunderstanding What Constitutes a "Special Substring"

A common mistake is thinking that any substring with equal 1s and 0s is special. However, a special substring must satisfy BOTH conditions:

  • Equal number of 1s and 0s
  • Every prefix has at least as many 1s as 0s

Incorrect assumption: Treating "0110" as special (it has equal counts but fails the prefix condition - the first character is 0).

Solution: The algorithm correctly identifies special substrings by tracking the balance and only considering a substring complete when balance returns to 0 after starting from a positive value (since special strings must start with 1).

2. Incorrectly Processing the Inner Content

When finding a special substring like "110100", developers might forget to:

  • Remove the outer 1 and 0 before recursive processing
  • Add them back after processing

Common mistake:

# Wrong: Processing the entire substring recursively
special_substring = self.makeLargestSpecial(s[start:i+1])

Correct approach:

# Right: Process only the inner content, then wrap with '1' and '0'
inner_content = self.makeLargestSpecial(s[start + 1:i])
special_substring = '1' + inner_content + '0'

3. Forgetting to Handle Multiple Special Substrings at the Same Level

The string might contain multiple consecutive special substrings at the root level. For example, "11001100" contains two special substrings: "1100" and "1100".

Pitfall: Assuming the entire string is always one special substring and returning after finding the first one.

Solution: The algorithm correctly continues searching for more special substrings after finding one by updating start = i + 1 and continuing the loop.

4. Incorrect Sorting Direction

Since we want the lexicographically largest result, substrings should be sorted in descending order.

Common mistake:

# Wrong: This would give the smallest result
substrings.sort()  # Default is ascending order

Correct approach:

# Right: Sort in descending order for largest result
substrings.sort(reverse=True)

5. Off-by-One Errors in Index Handling

When extracting the inner content, it's easy to make indexing errors:

Common mistakes:

  • Using s[start:i+1] instead of s[start+1:i] for inner content
  • Forgetting to update start = i + 1 (not just start = i)

These errors would either include the outer 1 and 0 in the recursive call or miss characters when searching for the next special substring.

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

In a binary min heap, the minimum element can be found in:


Recommended Readings

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

Load More