1702. Maximum Binary String After Change
Problem Description
You are given a binary string consisting of only 0s and 1s. You can perform two types of operations any number of times:
Operation 1: Replace any occurrence of substring "00"
with "10"
- Example:
"00010"
β"10010"
(the first two 0s become"10"
)
Operation 2: Replace any occurrence of substring "10"
with "01"
- Example:
"00010"
β"00001"
(the"10"
at the end becomes"01"
)
Your goal is to find the maximum binary string possible after applying these operations any number of times. A binary string x
is considered greater than binary string y
if the decimal value of x
is greater than the decimal value of y
.
The key insight is understanding how these operations work together:
- Operation 2 can effectively move 1s towards the right (since
"10"
becomes"01"
) - Operation 1 can convert consecutive 0s into a pattern with more 1s (since
"00"
becomes"10"
)
By strategically using these operations, you can rearrange the string to maximize its decimal value. The optimal strategy involves moving all 1s that aren't at the beginning to the end, then converting the middle 0s to maximize the number of leading 1s, resulting in at most one 0 in the final string.
Intuition
Let's think about what these operations actually do to maximize our binary string.
First, observe what each operation accomplishes:
- Operation 1 (
"00"
β"10"
): This converts two 0s into a 1 followed by a 0, effectively creating a 1 from nothing - Operation 2 (
"10"
β"01"
): This swaps a 1 and 0, moving the 1 to the right
To maximize a binary number, we want as many 1s as possible at the leftmost positions. So let's think strategically:
-
Moving 1s around: Using Operation 2 repeatedly, we can "bubble" any 1 to the right. For example:
"10"
β"01"
. If we have"100"
, we can do:"100"
β"010"
. This means we can move all 1s that aren't already at the beginning to the end of the string. -
Converting 0s to 1s: Once we've moved 1s out of the way, we'll have consecutive 0s in the middle. Using Operation 1, we can convert pairs of 0s:
"00"
β"10"
. If we have"0000"
, we can do:"0000"
β"1000"
β"1010"
β"1100"
β"1110"
. Notice that from n consecutive 0s, we end up with (n-1) 1s followed by a single 0. -
The optimal pattern: Combining these insights, the optimal strategy becomes clear:
- Keep all leading 1s in place (they're already in the best position)
- Move all other 1s to the end using Operation 2
- Convert the consecutive 0s in the middle to maximize 1s using Operation 1
The result will be a string with as many leading 1s as possible, followed by exactly one 0 (if there were any 0s to begin with), and then trailing 1s. This gives us the maximum possible binary value.
For example, "01101"
would become "11011"
through this process:
- Original:
"01101"
- After moving middle 1s to the end:
"00011"
- After converting the two 0s:
"10011"
- Continue operations:
"11011"
The key realization is that we can always achieve a configuration with at most one 0 in the string, and that 0 should be as far right as possible while maintaining all the 1s we can create.
Learn more about Greedy patterns.
Solution Approach
Based on our intuition, we know the optimal string will have at most one 0
, positioned as far right as possible while maximizing leading 1
s. Let's implement this insight:
Step 1: Check if there are any 0s
k = binary.find('0') if k == -1: return binary
If the string contains no 0
s (all 1
s), it's already maximum, so we return it as-is.
Step 2: Count total 0s that will be consolidated
k += binary[k + 1 :].count('0')
Here's the key insight:
k
initially holds the position of the first0
- We then count all
0
s that appear after this first0
usingbinary[k + 1:].count('0')
- Adding these gives us the final position where our single
0
will end up
Why does this work?
- All leading
1
s (before the first0
) stay in place - All
0
s in the string will be consolidated into consecutive0
s through Operation 2 (moving intervening1
s to the right) - These consecutive
0
s will then be converted to1
s except for one remaining0
through Operation 1 - The position of this final
0
is at indexk
(original first0
position + count of other0
s)
Step 3: Construct the result string
return '1' * k + '0' + '1' * (len(binary) - k - 1)
The final string has three parts:
'1' * k
: All positions before indexk
are1
s'0'
: Exactly one0
at positionk
'1' * (len(binary) - k - 1)
: All remaining positions are1
s
Example walkthrough:
For binary = "01101"
:
- First
0
is at position0
- Count of
0
s after position0
is1
(the0
at position 3) - So
k = 0 + 1 = 1
- Result:
'1' * 1 + '0' + '1' * 3 = "10111"
This elegant solution avoids simulating the actual operations and directly computes the final optimal configuration in O(n) time with O(1) extra space.
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 the example binary = "01101"
:
Step 1: Find the first 0
- The first
0
appears at index0
- So
k = 0
Step 2: Count remaining 0s
- After index 0, we look at substring
"1101"
- This contains one more
0
at position 3 of the original string - Count of remaining 0s =
1
- Update
k = 0 + 1 = 1
Step 3: Build the result
- We need
k
ones before the single0
:'1' * 1 = "1"
- Add the single
0
:"10"
- Add remaining ones:
'1' * (5 - 1 - 1) = '1' * 3 = "111"
- Final result:
"10111"
Verification through operations: Let's verify this is achievable:
- Start:
"01101"
- Use Operation 2 on index 2-3:
"01101"
β"01011"
(move the middle1
right) - Use Operation 2 on index 1-2:
"01011"
β"00111"
(move another1
right) - Use Operation 1 on index 0-1:
"00111"
β"10111"
(convert"00"
to"10"
)
The result "10111"
matches our calculated answer! The algorithm correctly identified that with 2 total 0
s in the original string, we can create a result with exactly one 0
at position 1, with all other positions being 1
s.
Solution Implementation
1class Solution:
2 def maximumBinaryString(self, binary: str) -> str:
3 # Find the index of the first '0' in the binary string
4 first_zero_index = binary.find('0')
5
6 # If there are no zeros, the string is already maximum (all ones)
7 if first_zero_index == -1:
8 return binary
9
10 # Count the number of zeros after the first zero
11 # This determines where the single '0' will be placed in the result
12 zeros_after_first = binary[first_zero_index + 1:].count('0')
13
14 # Calculate the position where the single '0' will be placed
15 # The position is: first_zero_index + number_of_zeros_after_first
16 zero_position = first_zero_index + zeros_after_first
17
18 # Construct the maximum binary string:
19 # - All '1's before the zero position
20 # - A single '0' at the calculated position
21 # - All '1's after the zero position
22 result = '1' * zero_position + '0' + '1' * (len(binary) - zero_position - 1)
23
24 return result
25
1class Solution {
2 public String maximumBinaryString(String binary) {
3 // Find the index of the first '0' in the binary string
4 int firstZeroIndex = binary.indexOf('0');
5
6 // If there are no '0's, the string is already maximum (all '1's)
7 if (firstZeroIndex == -1) {
8 return binary;
9 }
10
11 // Count the total number of '0's in the string
12 int zeroCount = 1; // Start with 1 for the first '0' found
13 int stringLength = binary.length();
14
15 for (int i = firstZeroIndex + 1; i < stringLength; ++i) {
16 if (binary.charAt(i) == '0') {
17 ++zeroCount;
18 }
19 }
20
21 // Calculate the position where the single '0' should be placed
22 // This will be at index: firstZeroIndex + (zeroCount - 1)
23 int finalZeroPosition = firstZeroIndex + zeroCount - 1;
24
25 // Create the result array with all '1's
26 char[] result = new char[stringLength];
27 Arrays.fill(result, '1');
28
29 // Place the single '0' at the calculated position
30 result[finalZeroPosition] = '0';
31
32 // Convert the character array to string and return
33 return String.valueOf(result);
34 }
35}
36
1class Solution {
2public:
3 string maximumBinaryString(string binary) {
4 // Find the position of the first '0' in the binary string
5 int firstZeroPos = binary.find('0');
6
7 // If there are no '0's, the string is already maximum (all '1's)
8 if (firstZeroPos == string::npos) {
9 return binary;
10 }
11
12 int stringLength = binary.size();
13 int zeroCount = firstZeroPos; // Initialize with position of first '0'
14
15 // Count the number of '0's after the first '0' position
16 for (int i = firstZeroPos + 1; i < stringLength; ++i) {
17 if (binary[i] == '0') {
18 ++zeroCount;
19 }
20 }
21
22 // Construct the maximum binary string:
23 // - 'zeroCount' ones at the beginning
24 // - One '0' at position 'zeroCount'
25 // - Remaining positions filled with '1's
26 return string(zeroCount, '1') + '0' + string(stringLength - zeroCount - 1, '1');
27 }
28};
29
1/**
2 * Converts a binary string to its maximum possible value by applying operations.
3 * Operations allowed:
4 * - "00" -> "10" (change "00" to "10")
5 * - "10" -> "01" (change "10" to "01")
6 *
7 * @param binary - The input binary string
8 * @returns The maximum binary string after applying operations
9 */
10function maximumBinaryString(binary: string): string {
11 // Find the index of the first '0' in the binary string
12 const firstZeroIndex: number = binary.indexOf('0');
13
14 // If there are no zeros, the string is already at maximum (all 1s)
15 if (firstZeroIndex === -1) {
16 return binary;
17 }
18
19 // Count the number of zeros after the first zero
20 // This determines where the final '0' will be positioned
21 const zerosAfterFirst: number = binary.slice(firstZeroIndex + 1).split('0').length - 1;
22
23 // Calculate the position of the single '0' in the result
24 const finalZeroPosition: number = firstZeroIndex + zerosAfterFirst;
25
26 // Build the result string:
27 // - All '1's before the final zero position
28 // - A single '0' at the calculated position
29 // - All '1's after the zero
30 const leftOnes: string = '1'.repeat(finalZeroPosition);
31 const rightOnes: string = '1'.repeat(binary.length - finalZeroPosition - 1);
32
33 return leftOnes + '0' + rightOnes;
34}
35
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs the following operations:
binary.find('0')
: This searches for the first occurrence of '0' in the string, which takesO(n)
time in the worst case.binary[k + 1:].count('0')
: This creates a substring from indexk+1
to the end (takingO(n)
time) and counts the occurrences of '0' in it (takingO(n)
time).- String concatenation
'1' * k + '0' + '1' * (len(binary) - k - 1)
: This creates a new string of lengthn
, which takesO(n)
time.
Overall, the time complexity is O(n)
where n
is the length of the input string.
Space Complexity: O(n)
The algorithm uses additional space for:
- The substring
binary[k + 1:]
which can be up toO(n)
characters in the worst case. - The output string created by concatenation, which has exactly
n
characters, requiringO(n)
space.
Therefore, the space complexity is O(n)
where n
is the length of the input string.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Final Zero Position
The Mistake:
Many developers incorrectly assume that the final 0
position should be calculated as:
zero_position = first_zero_index + total_zeros_in_string - 1
This comes from thinking that all zeros (including the first one) should be counted when determining the final position.
Why It's Wrong:
The first zero is already at position first_zero_index
. We only need to move it forward by the number of zeros that come after it, not by the total count of zeros.
Correct Approach:
zero_position = first_zero_index + zeros_after_first
Example to Illustrate:
For binary = "1001010"
:
- First zero at index 1
- Zeros after first zero: 2 (at indices 2 and 4)
- Wrong calculation:
1 + 3 - 1 = 3
β Result:"1110111"
(incorrect) - Correct calculation:
1 + 2 = 3
β Result:"1110111"
(correct)
Wait, both give the same answer here! Let's use binary = "00110"
:
- First zero at index 0
- Total zeros: 2
- Zeros after first: 1
- Wrong:
0 + 2 - 1 = 1
β Would give"10111"
- Correct:
0 + 1 = 1
β Gives"10111"
Actually, let me reconsider. The real pitfall is different:
Pitfall 1: Attempting to Simulate Operations Instead of Direct Calculation
The Mistake: Trying to actually perform Operation 1 and Operation 2 repeatedly:
def maximumBinaryString(self, binary: str) -> str:
result = list(binary)
changed = True
while changed:
changed = False
# Try Operation 1: "00" -> "10"
for i in range(len(result) - 1):
if result[i] == '0' and result[i+1] == '0':
result[i] = '1'
changed = True
break
# Try Operation 2: "10" -> "01"
for i in range(len(result) - 1):
if result[i] == '1' and result[i+1] == '0':
result[i], result[i+1] = '0', '1'
changed = True
return ''.join(result)
Why It's Wrong:
- This approach has exponential time complexity in worst cases
- It's hard to determine the optimal order of operations
- The solution may get stuck in local optima
Correct Approach:
Use the mathematical insight that the final result will always have at most one 0
, positioned based on the count of zeros in the original string.
Pitfall 2: Off-by-One Error in Final String Construction
The Mistake:
# Forgetting to subtract 1 for the zero position
result = '1' * zero_position + '0' + '1' * (len(binary) - zero_position)
Why It's Wrong:
This creates a string that's one character longer than the original because we're not accounting for the '0'
character when calculating the number of trailing 1
s.
Correct Approach:
result = '1' * zero_position + '0' + '1' * (len(binary) - zero_position - 1)
The -1
accounts for the single '0'
we've already placed.
Pitfall 3: Not Handling Edge Cases
The Mistake:
def maximumBinaryString(self, binary: str) -> str:
first_zero = binary.find('0')
zeros_after = binary[first_zero + 1:].count('0')
# Direct calculation without checking if first_zero is -1
return '1' * (first_zero + zeros_after) + '0' + '1' * (len(binary) - first_zero - zeros_after - 1)
Why It's Wrong:
If the string contains no zeros (e.g., "1111"
), find('0')
returns -1
, leading to incorrect string slicing and wrong results.
Correct Approach: Always check if zeros exist before proceeding:
if first_zero_index == -1: return binary
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!