926. Flip String to Monotone Increasing
Problem Description
You're given a binary string s
consisting of only '0's and '1's. A binary string is considered monotone increasing if it follows a specific pattern: first, there are some number of '0's (possibly zero), followed by some number of '1's (also possibly zero).
Examples of monotone increasing strings:
"0000"
(all zeros)"1111"
(all ones)"0011"
(zeros followed by ones)"01"
(zero followed by one)""
(empty string)
Examples of strings that are NOT monotone increasing:
"010"
(has a '0' after a '1')"10"
(starts with '1' then has '0')
Your task is to transform the given string s
into a monotone increasing string by performing flips. A flip operation means changing a character at position i
from '0' to '1' or from '1' to '0'.
The goal is to find the minimum number of flips needed to make the string monotone increasing.
For example:
- If
s = "00110"
, you could flip the '1's at positions 2 and 3 to get"00000"
(2 flips) - Or you could flip the last '0' at position 4 to get
"00111"
(1 flip) - The minimum is 1 flip
The solution approach considers each possible "split point" where all characters to the left should be '0' and all characters to the right should be '1', calculating the cost for each split and finding the minimum.
Intuition
To make a binary string monotone increasing, we need to ensure all '0's come before all '1's. This means we need to pick a "split point" in the string where everything to the left should be '0' and everything to the right should be '1'.
The key insight is that we can try every possible split point and calculate the cost for each. For any split point i
:
- All '1's to the left of (and including) position
i
need to be flipped to '0' - All '0's to the right of position
i
need to be flipped to '1'
Let's think about what information we need to calculate this cost efficiently:
- At each position
i
, we need to know how many '1's are to the left (these need flipping to '0') - We need to know how many '0's are to the right (these need flipping to '1')
Since the number of '1's to the left equals (i+1) - number_of_zeros_to_left
, and the number of '0's to the right equals total_zeros - number_of_zeros_to_left
, we can track just the cumulative count of '0's as we traverse the string.
The formula for the cost at position i
becomes:
- Number of '1's to flip to '0' =
(i+1) - cur
wherecur
is count of '0's up to positioni
- Number of '0's to flip to '1' =
tot - cur
wheretot
is total '0's in the string - Total cost =
(i+1) - cur + tot - cur
We also consider the edge case where we flip all '0's to '1's (making the entire string "1111..."), which costs tot
flips.
By trying all possible split points and keeping track of the minimum cost, we find the optimal solution.
Learn more about Dynamic Programming patterns.
Solution Approach
The implementation uses a prefix sum technique combined with enumeration of all possible split points.
Step 1: Count total zeros
tot = s.count("0")
First, we count the total number of '0's in the string. This gives us the cost of one extreme case: flipping all '0's to '1's to make the string all ones.
Step 2: Initialize variables
ans, cur = tot, 0
ans
is initialized totot
, representing the cost of making the entire string consist of only '1'scur
will track the cumulative count of '0's as we traverse the string
Step 3: Enumerate each position
for i, c in enumerate(s, 1):
cur += int(c == "0")
ans = min(ans, i - cur + tot - cur)
For each position i
(using 1-based indexing for easier calculation):
- Update
cur
by adding 1 if the current character is '0' - Calculate the cost if we make position
i
the last '0' in our monotone string:i - cur
: Number of '1's in positions[1, i]
that need to be flipped to '0'tot - cur
: Number of '0's in positions[i+1, n]
that need to be flipped to '1'- Total cost:
i - cur + tot - cur
- Update
ans
with the minimum cost found so far
Why this works:
- By using 1-based indexing (
enumerate(s, 1)
), positioni
represents the count of characters we've seen so far i - cur
gives us the number of '1's seen so far (total characters minus '0's)tot - cur
gives us the remaining '0's after positioni
- The formula
i - cur + tot - cur
calculates the exact number of flips needed if we split at positioni
Time Complexity: O(n)
where n
is the length of the string - we traverse the string twice (once for counting, once for enumeration)
Space Complexity: O(1)
- we only use a constant amount of extra space
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 s = "00110"
.
Step 1: Count total zeros
tot = 3
(there are three '0's in the string)- Initialize
ans = 3
(cost of flipping all '0's to make "11111") - Initialize
cur = 0
(cumulative count of '0's)
Step 2: Process each position
Position i=1, character '0':
cur = 0 + 1 = 1
(found one '0' so far)- Cost if we split here:
1 - 1 + 3 - 1 = 0 + 2 = 2
- Left side [0]: 0 ones to flip to '0'
- Right side [0110]: 2 zeros to flip to '1'
ans = min(3, 2) = 2
Position i=2, character '0':
cur = 1 + 1 = 2
(found two '0's so far)- Cost if we split here:
2 - 2 + 3 - 2 = 0 + 1 = 1
- Left side [00]: 0 ones to flip to '0'
- Right side [110]: 1 zero to flip to '1'
ans = min(2, 1) = 1
Position i=3, character '1':
cur = 2 + 0 = 2
(still two '0's total)- Cost if we split here:
3 - 2 + 3 - 2 = 1 + 1 = 2
- Left side [001]: 1 one to flip to '0'
- Right side [10]: 1 zero to flip to '1'
ans = min(1, 2) = 1
Position i=4, character '1':
cur = 2 + 0 = 2
- Cost if we split here:
4 - 2 + 3 - 2 = 2 + 1 = 3
- Left side [0011]: 2 ones to flip to '0'
- Right side [0]: 1 zero to flip to '1'
ans = min(1, 3) = 1
Position i=5, character '0':
cur = 2 + 1 = 3
- Cost if we split here:
5 - 3 + 3 - 3 = 2 + 0 = 2
- Left side [00110]: 2 ones to flip to '0'
- Right side []: 0 zeros to flip to '1'
ans = min(1, 2) = 1
Result: The minimum number of flips is 1. This corresponds to flipping the last '0' to get "00111".
Solution Implementation
1class Solution:
2 def minFlipsMonoIncr(self, s: str) -> int:
3 # Count total number of zeros in the string
4 total_zeros = s.count("0")
5
6 # Initialize minimum flips as flipping all zeros to ones
7 min_flips = total_zeros
8
9 # Track number of zeros encountered so far
10 zeros_seen = 0
11
12 # Iterate through each position as a potential split point
13 # Position i means: keep all as 0s before i, and all as 1s from i onwards
14 for position, char in enumerate(s, 1):
15 # Update count of zeros seen so far
16 if char == "0":
17 zeros_seen += 1
18
19 # Calculate flips needed for this split:
20 # - Flip all 1s before position to 0s: (position - zeros_seen)
21 # - Flip all 0s from position onwards to 1s: (total_zeros - zeros_seen)
22 ones_to_flip = position - zeros_seen
23 zeros_to_flip = total_zeros - zeros_seen
24 total_flips_at_split = ones_to_flip + zeros_to_flip
25
26 # Update minimum flips
27 min_flips = min(min_flips, total_flips_at_split)
28
29 return min_flips
30
1class Solution {
2 public int minFlipsMonoIncr(String s) {
3 int n = s.length();
4
5 // Count total number of zeros in the string
6 int totalZeros = 0;
7 for (int i = 0; i < n; i++) {
8 if (s.charAt(i) == '0') {
9 totalZeros++;
10 }
11 }
12
13 // Initialize minimum flips with the cost of flipping all zeros to ones
14 // (making the entire string all ones)
15 int minFlips = totalZeros;
16
17 // Count zeros encountered so far while iterating
18 int zerosSeenSoFar = 0;
19
20 // Try each position as a split point where:
21 // - All positions before index i should be 0
22 // - All positions from index i onwards should be 1
23 for (int i = 1; i <= n; i++) {
24 // Update count of zeros seen up to position i-1
25 if (s.charAt(i - 1) == '0') {
26 zerosSeenSoFar++;
27 }
28
29 // Calculate flips needed for this split:
30 // - (i - zerosSeenSoFar): number of ones before position i that need to flip to 0
31 // - (totalZeros - zerosSeenSoFar): number of zeros from position i onwards that need to flip to 1
32 int flipsNeeded = (i - zerosSeenSoFar) + (totalZeros - zerosSeenSoFar);
33
34 // Update minimum flips if current split is better
35 minFlips = Math.min(minFlips, flipsNeeded);
36 }
37
38 return minFlips;
39 }
40}
41
1class Solution {
2public:
3 int minFlipsMonoIncr(string s) {
4 // Count total number of '0's in the entire string
5 int totalZeros = count(s.begin(), s.end(), '0');
6
7 // Initialize minimum flips with the case of converting all '0's to '1's
8 // (making the entire string all '1's)
9 int minFlips = totalZeros;
10
11 // Count of '0's encountered so far while iterating
12 int zerosSeenSoFar = 0;
13
14 // Try each position as a potential split point
15 // Everything before position i should be '0', everything from i onwards should be '1'
16 for (int i = 1; i <= s.size(); ++i) {
17 // Update count of zeros seen up to position i-1
18 if (s[i - 1] == '0') {
19 zerosSeenSoFar++;
20 }
21
22 // Calculate flips needed for this split:
23 // - Flip all '1's before position i to '0': (i - zerosSeenSoFar)
24 // - Flip all '0's from position i onwards to '1': (totalZeros - zerosSeenSoFar)
25 int flipsNeeded = (i - zerosSeenSoFar) + (totalZeros - zerosSeenSoFar);
26
27 // Update minimum flips if current split is better
28 minFlips = min(minFlips, flipsNeeded);
29 }
30
31 return minFlips;
32 }
33};
34
1/**
2 * Finds the minimum number of flips to make a binary string monotone increasing.
3 * A monotone increasing string has all 0s before all 1s.
4 *
5 * @param s - Binary string containing only '0' and '1'
6 * @returns Minimum number of character flips needed
7 */
8function minFlipsMonoIncr(s: string): number {
9 // Count total number of zeros in the string
10 // These represent potential flips if we make the entire string '1'
11 let totalZeros: number = 0;
12 for (const char of s) {
13 totalZeros += char === '0' ? 1 : 0;
14 }
15
16 // Initialize minimum flips with the case of flipping all zeros to ones
17 let minFlips: number = totalZeros;
18
19 // Count zeros encountered so far while iterating
20 let zerosSeenSoFar: number = 0;
21
22 // Try each position as a potential split point between 0s and 1s
23 // Everything before position i should be 0, everything from i onwards should be 1
24 for (let i = 1; i <= s.length; ++i) {
25 // Update count of zeros seen up to position i-1
26 zerosSeenSoFar += s[i - 1] === '0' ? 1 : 0;
27
28 // Calculate flips needed at this split point:
29 // - (i - zerosSeenSoFar): ones before position i that need to flip to 0
30 // - (totalZeros - zerosSeenSoFar): zeros from position i onwards that need to flip to 1
31 const flipsAtCurrentSplit: number = (i - zerosSeenSoFar) + (totalZeros - zerosSeenSoFar);
32
33 // Update minimum flips if current split is better
34 minFlips = Math.min(minFlips, flipsAtCurrentSplit);
35 }
36
37 return minFlips;
38}
39
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. This is because:
- The
count("0")
operation traverses the entire string once, takingO(n)
time - The
enumerate()
loop iterates through each character in the string exactly once, performing constant-time operations (int(c == "0")
, addition, andmin()
comparison) in each iteration, takingO(n)
time - Total:
O(n) + O(n) = O(n)
The space complexity is O(1)
. The algorithm only uses a fixed number of variables (tot
, ans
, cur
, i
, c
) regardless of the input size, requiring constant extra space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error with Split Point Interpretation
A common mistake is misunderstanding what the split point represents. Developers often confuse whether position i
is the last '0' or the first '1' in the final monotone string.
Incorrect Approach:
def minFlipsMonoIncr(self, s: str) -> int:
total_zeros = s.count("0")
min_flips = total_zeros
zeros_seen = 0
for i in range(len(s)): # Using 0-based indexing
if s[i] == "0":
zeros_seen += 1
# Wrong calculation - mixing 0-based index with count logic
min_flips = min(min_flips, i - zeros_seen + total_zeros - zeros_seen)
return min_flips
Why it fails: Using 0-based indexing while trying to calculate the number of characters processed leads to incorrect flip counts.
Correct Solution:
Use 1-based enumeration to ensure position
represents the count of characters processed, making the arithmetic intuitive.
2. Forgetting Edge Cases
Another pitfall is not considering the cases where the optimal solution is to make the entire string all '0's or all '1's.
Incomplete Approach:
def minFlipsMonoIncr(self, s: str) -> int:
total_zeros = s.count("0")
min_flips = float('inf') # Missing initialization with edge case
zeros_seen = 0
for position, char in enumerate(s, 1):
if char == "0":
zeros_seen += 1
min_flips = min(min_flips, position - zeros_seen + total_zeros - zeros_seen)
return min_flips
Why it fails: If the string is already all '1's, we never update min_flips
properly. Also doesn't consider making everything '0's.
Correct Solution:
Initialize min_flips
with total_zeros
(cost of making everything '1's). The algorithm naturally handles making everything '0's when it processes the last position.
3. Incorrect Formula for Calculating Flips
A subtle error is using the wrong formula when calculating the number of flips needed at each split point.
Wrong Formula:
def minFlipsMonoIncr(self, s: str) -> int:
total_zeros = s.count("0")
min_flips = total_zeros
zeros_seen = 0
for position, char in enumerate(s, 1):
if char == "0":
zeros_seen += 1
# Wrong: double-counting or using wrong variables
min_flips = min(min_flips, zeros_seen + (len(s) - position - (total_zeros - zeros_seen)))
return min_flips
Why it fails: The formula doesn't correctly calculate the number of '1's to flip before the split and '0's to flip after the split.
Correct Formula:
- '1's before position
i
that need flipping:position - zeros_seen
- '0's after position
i
that need flipping:total_zeros - zeros_seen
- Total:
(position - zeros_seen) + (total_zeros - zeros_seen)
4. Not Handling Empty String or Single Character
While the algorithm naturally handles these cases, explicitly checking can prevent unexpected behavior in some implementations.
Better Practice:
def minFlipsMonoIncr(self, s: str) -> int:
if not s or len(s) <= 1:
return 0
# Continue with main algorithm...
This makes the code more readable and explicitly shows that edge cases have been considered.
In a binary min heap, the maximum element can be found in:
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!