A monotone increasing binary string consists of some number of 0s followed by some number of 1s. At each position, we need to decide whether to keep the character as is or flip it. The key insight is that we can track whether we are still in the "all zeros" portion or have transitioned to the "all ones" portion.
If we are still allowed to have zeros (mono = true), we can either keep a 0 or flip a 1 to 0, or we can transition to the ones portion. Once we commit to having only 1s, any 0 we encounter must be flipped. This recursive structure with memoization efficiently explores all valid ways to partition the string.
dfs(i, mono) where i is the current index and mono indicates whether we can still place zeros.i equals the string length, return 0 (no more flips needed).mono is true and the current character is '0', we can either keep it (continue with zeros) or flip it to 1 and switch to ones-only mode.mono is true and the current character is '1', we can either keep it (switch to ones-only) or flip it to 0 (continue with zeros).mono is false (ones-only mode), keep 1s as is and flip any 0s.0 with mono = true.class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
dp = {}
def dfs(i, mono):
if (i, mono) in dp:
return dp[(i, mono)]
if i == len(s):
return 0
if mono and s[i] == "0":
dp[(i, mono)] = min(1 + dfs(i + 1, False), dfs(i + 1, mono))
elif mono and s[i] == "1":
dp[(i, mono)] = min(1 + dfs(i + 1, mono), dfs(i + 1, False))
elif not mono and s[i] == "1":
dp[(i, mono)] = dfs(i + 1, mono)
else:
dp[(i, mono)] = 1 + dfs(i + 1, mono)
return dp[(i, mono)]
return dfs(0, True)Instead of recursion, we can iterate through the string from right to left and build up the solution. For each position, we track the minimum flips needed if that position and everything after it should be all 1s versus if we are still in the flexible zone where 0s are allowed.
Processing from right to left lets us use already-computed results for positions ahead of the current one. The state transitions mirror the top-down approach but avoid recursion overhead.
dp[i][1] represents minimum flips from index i when we can still have zeros, and dp[i][0] when we must have all ones.dp[n][0] = dp[n][1] = 0 (no characters left means no flips).n-1 down to 0.'0':1 and switch modes.1.'1':0 or keep it and switch to ones-only.dp[0][1] as the answer.class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
n = len(s)
dp = [[0] * 2 for _ in range(n + 1)]
for i in range(n - 1, -1, -1):
if s[i] == '0':
dp[i][1] = min(1 + dp[i + 1][0], dp[i + 1][1])
dp[i][0] = 1 + dp[i + 1][0]
else: # s[i] == '1'
dp[i][1] = min(1 + dp[i + 1][1], dp[i + 1][0])
dp[i][0] = dp[i + 1][0]
return dp[0][1]The bottom-up solution only needs the DP values from the next position to compute the current position. This means we do not need an entire 2D array; just two variables suffice.
By maintaining only the previous row's values, we reduce space from O(n) to O(1) while preserving the same logic.
dp[0] and dp[1] to 0 (representing the state after processing the entire string).newDp0 (ones-only mode) and newDp1 (flexible mode) based on the current dp values.'0': flexible mode chooses the minimum between keeping it or flipping; ones-only mode must flip.'1': flexible mode chooses between flipping to 0 or switching to ones-only; ones-only mode keeps it.dp with the new values.dp[1].class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
n = len(s)
dp = [0, 0]
for i in range(n - 1, -1, -1):
if s[i] == '0':
new_dp_1 = min(1 + dp[0], dp[1])
new_dp_0 = dp[0] + 1
else: # s[i] == '1'
new_dp_1 = min(dp[1] + 1, dp[0])
new_dp_0 = dp[0]
dp[1] = new_dp_1
dp[0] = new_dp_0
return dp[1]A monotone increasing string is all 0s followed by all 1s. We can think of the string as being split at some index: everything before is 0, everything after is 1. For each possible split point, we need to flip all 1s on the left to 0 and all 0s on the right to 1.
Precomputing prefix counts of 1s and suffix counts of 0s lets us evaluate each split point in O(1) time. The answer is the minimum sum across all split points.
leftOnes where leftOnes[i] counts the number of 1s in indices 0 to i-1.rightZeros where rightZeros[i] counts the number of 0s in indices i to n-1.i from 0 to n, the cost is leftOnes[i] + rightZeros[i].class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
n = len(s)
left_ones = [0] * (n + 1)
right_zeros = [0] * (n + 1)
for i in range(n):
left_ones[i + 1] = left_ones[i] + (1 if s[i] == '1' else 0)
for i in range(n - 1, -1, -1):
right_zeros[i] = right_zeros[i + 1] + (1 if s[i] == '0' else 0)
res = float('inf')
for i in range(n + 1):
res = min(res, left_ones[i] + right_zeros[i])
return resWe can solve this problem in a single pass by maintaining a running count of 1s seen so far and the minimum flips needed. When we see a 1, it might need to be flipped later if we decide that position should be 0. When we see a 0, we can either flip it to 1 (incrementing our flip count) or flip all previous 1s to 0.
The key insight is that the minimum flips at any position equals the minimum of: (1) flipping this 0 plus the previous minimum, or (2) flipping all 1s seen so far to 0s.
res (minimum flips) and cntOne (count of 1s seen) to 0.'1', increment cntOne.'0', update res = min(res + 1, cntOne):res + 1 means flip this 0 to 1.cntOne means flip all previous 1s to 0s instead.res.A monotone increasing binary string means all 0s come before all 1s. Some incorrectly interpret this as strictly increasing values or allow alternating patterns. The valid forms are only: all zeros, all ones, or zeros followed by ones with exactly one transition point.
The optimal solution relies on tracking how many 1s have been seen so far. When encountering a 0, you must decide whether to flip it to 1 or flip all previous 1s to 0. Forgetting to maintain this count leads to incorrect flip calculations.
When using the prefix-suffix approach, the split point represents where zeros end and ones begin. The valid split points range from 0 (all ones) to n (all zeros). Missing the boundary cases or incorrectly indexing the prefix/suffix arrays causes wrong answers.