761. Special Binary String
Problem Description
You're given a special type of binary string that has two key properties:
- It contains an equal number of
0
s and1
s - At any point while reading the string from left to right, you'll always have seen at least as many
1
s as0
s (every prefix has more or equal1
s than0
s)
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 1
s 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.
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:
- A concatenation of multiple special binary strings, or
- A string of the form
"1" + inner + "0"
whereinner
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 1
s equals the count of 0
s), 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 1
s 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:
-
Base Case: If the string is empty, return an empty string.
-
Initialize Variables:
ans
- list to store all special substrings found at the current levelcnt
- counter to track the balance between1
s and0
si
- current position pointerj
- start position of the current special substring
-
Iterate Through the String:
- For each character at position
i
:- If it's
'1'
, incrementcnt
by 1 - If it's
'0'
, decrementcnt
by 1
- If it's
- When
cnt
becomes 0, we've found a complete special substring from positionj
toi
- For each character at position
-
Process Each Special Substring:
- Extract the inner part:
s[j+1 : i]
(excluding the outer1
and0
) - 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
- Extract the inner part:
-
Sort and Combine:
- Sort all special substrings in
ans
in descending order (reverse=True) - Join them together to form the final result
- Sort all special substrings in
Example Walkthrough:
For input s = "11011000"
:
- First iteration finds
"1100"
whencnt
reaches 0 at index 3- Inner part is
"10"
, recursively processed - Reconstructed as
"1" + "10" + "0" = "1100"
- Inner part is
- Second iteration finds
"110000"
whencnt
reaches 0 at index 7- Inner part is
"1000"
, recursively processed - Reconstructed as
"1" + "1000" + "0" = "110000"
- Inner part is
- 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 EvaluatorExample 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
toi=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
toi=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 takesO(n)
time - The sorting operation
ans.sort(reverse=True)
takesO(k log k)
wherek
is the number of substrings at that level, butk β€ 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 1
s and 0
s is special. However, a special substring must satisfy BOTH conditions:
- Equal number of
1
s and0
s - Every prefix has at least as many
1
s as0
s
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
and0
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 ofs[start+1:i]
for inner content - Forgetting to update
start = i + 1
(not juststart = 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.
In a binary min heap, the minimum element can be found in:
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Donβt Miss This!