Problem Statement in English
You are given a binary string s. You can flip s[i] changing it from '0' to '1' or from '1' to '0'.
Return the minimum number of flips to make s monotone increasing.
Monotone increasing means that for all i <= j, s[i] <= s[j].
So a string like "000111" is monotone increasing, while "001010" is not. Also, "000" and "111" are monotone increasing.
Approach
The intuitive solution to this is simply to try every possible partition of the string into two parts: the left part being all 0s and the right part being all 1s. Along with that, we also need to consider the edge cases where the entire string is all 0s or all 1s.
This solution can be implemented using prefix and suffix sums.
But the crazy solution is both less intuitive and way less code.
The idea is that we can build the solution for any index i based on the solution for index i-1.
Think about it. If we know the minimum flips until the previous index, can we arrive at the number of flips when we include the current index?
Note that we’re not looking for the string itself, just the number of flips. This makes it easier. Tricky.
So, let’s try all cases:
If the current character is
1, then we don’t need to do anything. The number of flips remains the same as before. This is because any string ending in1is valid as long as the prefix is valid. Take for example000111or00111or1111. All valid.If the current character is
0, then we have two choices:Flip this
0to1. In this case, the number of flips increases by1compared to the previous index.Keep this
0as is. But for this to be valid, we need to ensure that all previous characters are also0s. This means we need to flip all previous1s to0s. The number of flips in this case would be equal to the number of1s encountered so far.
So in reality, we just need to keep track of two things as we iterate through the string:
- The number of
1s encountered so far. - The minimum number of flips calculated so far.
Reading the code will make this clearer.
Solution in Python
- Straighforward Prefix-Suffix Sum Solution
class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
N = len(s)
otl = [0] * N
zfh = [0] * N
zfh[-1] = int(s[-1] == "0")
ones = zeroes = 0
res = N
for i in range(1, N):
otl[i] = otl[i - 1] + int(s[i - 1] == "1")
for i in reversed(range(N - 1)):
zfh[i] = zfh[i + 1] + int(s[i] == "0")
for c in s:
if c == "1":
ones += 1
else:
zeroes += 1
# everything until and excluding this point be 0, and everything else be 1
for i in range(N):
res = min(res, otl[i] + zfh[i])
# everything be 0
res = min(res, N - ones)
# everything be 1
res = min(res, N - zeroes)
return res
- Optimal Dynamic Programming Solution
class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
N = len(s)
ones = 0
res = N
for c in s:
if c == "1":
ones += 1
else:
res = min(ones, res + 1)
return min(res, N - ones)
Complexity
Time:
Since we are iterating through the string only once.Space:
We are using only a constant amount of extra space.
Mistakes I Made
I had to look up the optimal solution. It was not intuitive to me at all.
And we are done.