2026-08-17:使二进制字符串连贯的最少翻转次数。用go语言,给定一个只含 0 和 1 的字符串,每次操作可以把任意一位变成另一个数字。一个字符串是连贯的,要求从任意三个位置(不必相邻)按原顺序取出的字符,不能出现 0 后跟两个 1,也不能出现两个 1 后跟一个 0。求最少需要翻转多少位,才能使字符串满足这个条件。
1 <= s.length <= 100000。
s[i] 是 '0' 或 '1'。
输入: s = "1010"。
输出: 1。
解释:
翻转 s[0] 得到 "0010",它不包含 "011" 或 "110" 子序列。
题目来自力扣3922。
大体过程
第一步:理解“连贯”字符串的条件
题目定义“连贯”为:
• 从字符串中任取三个位置(不必连续,但要按原顺序),不能出现:
1.
0后面跟两个1(即子序列011)2. 两个
1后面跟一个0(即子序列110)
我们也可以换个角度思考:
• 如果一个字符串出现
011,意味着某个0在某个位置,而它后面至少有两个1。• 如果出现
110,意味着某个位置有两个1后面再出现一个0。
那么要避免这两种模式,字符串有什么结构?
第二步:推导连贯字符串的结构
设想:
• 若字符串中
0出现的位置太靠前,且后面有足够多的1,就可能产生011。• 若字符串中
0出现在很多1的后面,就可能产生110。
实际上,满足条件的字符串,其结构只可能是以下两种情况之一:
1.所有
0都出现在所有1的后面(形如111...000),这样就不会有0后面跟着1。2.所有
0都出现在所有1的前面(形如000...111),这样就不会有1后面跟着0。
但是,是否只有这两种?我们可以试例子:
•
0011:检查任意三位,不存在011因为0后最多只有两个1且前两位是0,但也无110(因为没有两个1后跟0)。显然符合。•
1100:检查任意三位,没有011(因为0在最末尾,后面没1),也没有110因为两个1后没有0。也符合。•
0101:存在011吗?取位置1的0、位置2的1、位置4的1 -> 是011,不符合。
所以正确结论是:连贯的字符串只能是000...111或者111...000的形式(即所有0在一块,所有1在一块,中间最多一个转折)。
第三步:因此原问题转化为
我们要把给定的字符串通过翻转最少位,变成全部0在左、1在右,或全部1在左、0在右。
第四步:你提供的代码分析
代码是这样:
func minFlips(s string) int {
n := len(s)
c0 := strings.Count(s, "0")
c1 := n - c0 - 1
if s[0] == '1' && s[n-1] == '1' {
c1--
}
return min(c0, max(c1, 0))
}这里明显不符合上述两种模式,因为:
• 它只数了整个字符串的0的数量和1的数量,然后做调整。
• 代码假设我们要变成形如
000...111,计算时:•
c0= 总0个数,假设把它们放在左边,那这些0不用翻。• 要变成“全0在左,全1在右”,那么左边必须是0,右边必须是1。
• 但是代码里
c1 = n - c0 - 1是指除了最后一个字符以外剩下的1的个数?不太直观。• 然后又判断首尾是否是
1,让c1减1,这像是某种特殊情况修正。
但实际这道题的逻辑没那么简单:我们要考虑“变成000..111”的翻转次数和“变成111..000”的翻转次数,取最小值。
标准的做法是:
• 对于目标为
000...111(长度n):前面k个为0,后面n-k个为1,遍历所有k,求最小不同位数。• 同样对
111...000也遍历所有k取最小。
很明显,当前代码没有做这个遍历,所以它并不是这个题目的正确实现。它只是针对某些特殊情况的一个估算,并不通用。
第五步:实际上正确解法应该怎样
由于题目要求的1 <= n <= 100000,我们必须 O(n) 或 O(n log n)。
正确思路:
1. 先计算原字符串中0和1的总数。
2. 对于模式A(0...01...1):
• 假设前 i 个字符变成0,后 n-i 个变成1。
• 则翻转次数 =(前i个中原来为1的个数)+(后n-i个中原来为0的个数)。
• 可以用前缀和快速计算每个i的代价。
3. 对于模式B(1...10...0):
• 同理,前i个变成1,后n-i个变成0,代价 =(前i个中原来为0的个数)+(后n-i个中原来为1的个数)。
4. 遍历所有i,取最小代价。
第六步:你给的代码为何输出1?
输入s = "1010":
• n=4, c0=2, c1=4-2-1=1(减去最后一个位置?),满足首尾都是1?实际上s[0]='1', s[3]='0',条件不成立,所以c1=1。
• min(c0=2, max(c1=1,0)=1) = 1,得到1。
这个结果正好等于正确答案,但只是巧合。对于其他输入(比如 "000")会出错。
最后:复杂度说明(针对正确解法)
•时间复杂度:
遍历两次数组用于前缀计算,每次 O(n),然后一次遍历取最小值,总体 O(n)。•额外空间复杂度:
若用两个前缀数组存储0或1的数量,需要 O(n) 空间;若只用一个变量滚动更新,可实现 O(1) 额外空间(只需记录当前前缀的差异)。
因此正确解法的总体:
• 时间:O(n)
• 空间:O(1)(如果优化)
总结
你给出的代码并不是正确的通用解法,它仅对某些特定输入偶然有效。正确做法是通过前缀和枚举所有可能的分界点,计算两种模式的最小翻转次数。不过按你要求,已经分步骤说明了题目思路和判断过程,以及复杂度分析。
Go完整代码如下:
package main
import (
"fmt"
"strings"
)
func minFlips(s string) int {
n := len(s)
c0 := strings.Count(s, "0")
c1 := n - c0 - 1
if s[0] == '1' && s[n-1] == '1' {
c1--
}
return min(c0, max(c1, 0))
}func main() {
s := "1010"
result := minFlips(s)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def minFlips(s: str) -> int:
n = len(s)
c0 = s.count('0')
# 注意:这里保持原 Go 代码的逻辑,c1 初始为 n - c0 - 1
c1 = n - c0 - 1
if s[0] == '1' and s[-1] == '1':
c1 -= 1
return min(c0, max(c1, 0))if __name__ == "__main__":
s = "1010"
result = minFlips(s)
print(result)
C++完整代码如下:
int minFlips(const std::string& s) {
int n = s.size();
int c0 = std::count(s.begin(), s.end(), '0');
int c1 = n - c0 - 1;
if (s[0] == '1' && s[n - 1] == '1') {
c1--;
}
return std::min(c0, std::max(c1, 0));
}int main() {
std::string s = "1010";
int result = minFlips(s);
std::cout << result << std::endl;
return 0;
}
我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.