题目
给你一个 二进制字符串 s。
你可以对这个字符串执行 任意次 下述操作:
- 选择字符串中的任一下标
i(i + 1 < s.length),该下标满足s[i] == '1'且s[i + 1] == '0'。 - 将字符
s[i]向 右移 直到它到达字符串的末端或另一个'1'。例如,对于s = "010010",如果我们选择i = 1,结果字符串将会是s = "0**001**10"。
返回你能执行的 最大 操作次数。
示例 1:
输入: s = "1001101"
输出: 4
解释:
可以执行以下操作:
- 选择下标
i = 0。结果字符串为s = "**001**1101"。 - 选择下标
i = 4。结果字符串为s = "0011**01**1"。 - 选择下标
i = 3。结果字符串为s = "001**01**11"。 - 选择下标
i = 2。结果字符串为s = "00**01**111"。
示例 2:
输入: s = "00111"
输出: 0
提示:
1 <= s.length <= 10^5s[i]为'0'或'1'。
解题思路
题目描述的操作是:“选择一个 1,将其向右移动,直到遇到字符串末尾或另一个 1”。
这个操作隐含了两个关键信息:
- 跨越成本:虽然题目说是“移动”,但实际上是一个
1跳过了一段连续的0。无论这段0有多长(比如0还是00000),对于这一个1来说,这只是一次操作。 - 聚类效应(关键):为了让操作次数最大化,我们不应该把右边的
1先移走,而应该先把左边的1尽可能地往右移,让它们和右边的1聚在一起。- 为什么?因为当 $N$ 个
1聚在一起时,如果它们面前出现了一个“空隙”(一段0),这 $N$ 个1每一个都能执行一次跨越操作。这比它们分散开来单独跨越赚得更多。
- 为什么?因为当 $N$ 个
结论:这道题本质上是在计算**“雪球”滚过“坑”的总代价**。
- 雪球:当前累积的
1的数量。 - 坑:一段连续的
0。 - 规则:每遇到一个“坑”,当前手里所有的
1都要交一次“过路费”(操作数 +1)。
我们不需要真的去移动字符串数组(那会涉及大量的内存写操作),只需要遍历统计。
逻辑流:
- 我们从左到右扫描字符串。
- 我们需要一个变量
count来记录当前“雪球”里有多少个1。 - 我们需要一个变量
ans来记录总操作数。 - 状态转移:
- 遇到
1:count++。雪球变大了,但此时不产生操作数(因为还没遇到坑)。 - 遇到
0:意味着出现了“坑”。此时,所有累积的1都可以跨越这个坑。 - 去重细节:连续的多个
0视作同一个坑(同一个 gap)。只有从1进入0的那个瞬间,才意味着我们要支付过路费。
- 遇到
与其写复杂的 while 循环去跳过连续的 0,不如只关注状态跳变的瞬间。
我们关注的是模式 10。
- 条件:
s[i] == '1'且s[i+1] == '0'。 - 意义:这标志着一段
1的结束和一段0的开始。 - 动作:
- 因为
s[i] == '1',先把这个1加入雪球 (count++)。 - 因为
s[i+1] == '0',说明前方立刻就是悬崖(坑)。此时,手里所有的1(包括刚才加进去的那个)都要执行一次操作。于是ans += count。
- 因为
如果后面还有连续的 0(例如 100)?
- 当
i走到第一个0时,s[i]不等于1,逻辑直接跳过。这完美地实现了“连续0不重复计算”。
具体代码
func maxOperations(s string) int {
count := 0
ans := 0
for i := range s[:len(s)-1] {
if s[i] == '1' {
count++
if s[i + 1] == '0' {
ans += count
}
}
}
return ans
}