文章目录

题目描述

如果一个二进制字符串,是以一些 0(可能没有 0)后面跟着一些 1(也可能没有 1)的形式组成的,那么该字符串是 单调递增 的。
给你一个二进制字符串 s,你可以将任何 0 翻转为 1 或者将 1 翻转为 0 。
返回使 s 单调递增的最小翻转次数。

示例 1:
输入:s = “00110”
输出:1
解释:翻转最后一位得到 00111.

示例 2:
输入:s = “010110”
输出:2
解释:翻转得到 011111,或者是 000111。

示例 3:
输入:s = “00011000”
输出:2
解释:翻转得到 00000000

思路分析

用穷举的方式找到所有的可能,然后在此过程中记录最小操作数。

答案必定是 N个0 +N个1 的组合方式,N可以为0。

比如对于题目示例1所给出的字符串:
s = “00110”

我们可以得到的全部可能:

  • 00000 操作2次 (1变成0)
  • 00001 操作3次
  • 00011 操作2次
  • 00111 操作1次
  • 01111 操作2次
  • 11111 操作3次

可以看到只有将最后的0变成1是最小的操作次数。

我们假设一个记号X,让X不断的从字符串开头移动到字符串末尾,移动的过程中 要将X的前面的1变成0,X后面的0变成1,过程中记录最小的操作次数即可。

比如 当X下标为0的时候 字符串为 X00110 ,X前面没有数字,X后面0有三个,所以操作次数为3。

在这个思路中加入前缀和。 建立一个数组记录X前面需要改变的字符数。

例如 s = “00110” 则前缀和数组为 [0, 0, 0, 1, 2, 2].

这里其实比较好想,当记号X下标为0,1,2,时。s= X00110,s=0X0110,s=00X110

这三个字符串X前面需要改变的字符数都是0(即前面为1的个数)

当X下标为3时,即 s= 001X10 , X前面需要改变的字符数为1,所以前缀和数组下标为3的地方是1,以此类推。

建立前缀和数组的时候要小心,要搞清楚有多少个数,标记点是从0开始到最后一个字符的后面,所以比s长度多一位。

拿到前缀和数组之后,就要计算当前字符的总操作数,就是记号X前面的操作数(有多少1)+X后面的操作数(有多少0)。

X前面的操作数就是前缀和中当前下标的数,而X后面需要操作的数需要计算一下(实际上就是算X后面有多少个0):

  • 由 S的长度减去当前的下标 得到 当前标记X后面有多少个字符。
  • 前缀和数组的最后一位数记录了字符串s里有多少个1。所以当前下标的前缀和记录了X之前有多少个1,他俩做减法就得到了 当前X之后有多少个1。
  • 上面两步做减法,就得到了X后面又多少个0。

说的可能比较乱,来个示例看一下 例如 s = “00110” 则前缀和数组为 temp = [0, 0, 0, 1, 2, 2].

假设此时X的下标为i = 3 即 s= 001X10

计算此时最小操作步骤过程:

  • len(s) - i = 2 ,(2就是X后面还有几个字符)
  • temp[len(s)] - temp[i] = 1, (从前缀和数组上看,整个字符串有多少个1,当前下标i前面有多少个1.相减即是当前下标i后面有多少个1.)
  • (len(s) - i -(temp[len(s)] - temp[i]) = 1 ,(上面两步相减,得到当前记号X后面有多少个0,即X后面需要操作的次数)

最后X前面和X后面需要操作的次数相加即可。

完整代码

class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
temp = [0 for _ in range(len(s)+1)] # 前缀和数组 记录标记左边有多少个1
# 前缀和要搞清楚有多少个数,标记点是从0开始到最后一个字符的后面,所以比s长度多一位。
# 左边的1变成0,右边的0变成1 = 左边有多少个1 右边有多少个0
i = 0
for i in range(len(s)):
temp[i+1] = temp[i] + (ord(s[i]) - ord('0'))
res = 99999999
for i in range(len(s)+1):
res = min(res,temp[i] + (len(s) - i -(temp[len(s)] - temp[i])))
# n-i 当前标记后面有多少个字符,
# 后面的减法, 总共有多少个1-标记点之前的1 这块就等于从标记点到最后的1的数量
print(res)
return