题目
给定一个字符串 s,统计并返回具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是成组连续的。
重复出现(不同位置)的子串也要统计它们出现的次数。
示例 1:
输入:s = "00110011" 输出:6 解释:6 个子串满足具有相同数量的连续 1 和 0 :"0011"、"01"、"1100"、"10"、"0011" 和 "01" 。 注意,一些重复出现的子串(不同位置)要统计它们出现的次数。 另外,"00110011" 不是有效的子串,因为所有的 0(还有 1 )没有组合在一起。
示例 2:
输入:s = "10101" 输出:4 解释:有 4 个子串:"10"、"01"、"10"、"01" ,具有相同数量的连续 1 和 0 。
提示:
1 <= s.length <= 10^5s[i]为'0'或'1'
解题思路
这道题的核心意思:在给定的字符串中,找出一组连续的 0 和一组连续的 1 挨在一起的子串,并且这组 0 和这组 1 的数量必须完全相等。
我们可以把题目要求拆解成三个必须同时满足的条件:
- 连续子串:必须是原字符串中连在一起的一段。
- 0和1数量相等:子串里有多少个
0,就必须有多少个1。 - 成组连续(最关键的一点):子串里的
0必须全部挨在一起,1也必须全部挨在一起。也就是说,只能是00...11...或者11...00...的形式。- ✅ 有效:"01", "10", "0011", "111000"
- ❌ 无效:"0101"(0和1交替出现,没有各自抱团),"001100"(0被1隔开了)
这道题如果用双指针去暴力匹配会比较低效。既然要求 0 和 1 各自成组,我们其实可以把原字符串按字符的连续长度进行压缩统计。
比如 s = "00110011":
- 连续的
0有 2 个 - 连续的
1有 2 个 - 连续的
0有 2 个 - 连续的
1有 2 个压缩成长度数组就是:[2, 2, 2, 2]。
规律就在这里:任意两个相邻的组(比如有 $u$ 个 0 和相邻的 $v$ 个 1),它们能构成的有效子串数量,恰好就是 min(u, v)。
比如长度数组里相邻的两个 2,min(2, 2) = 2,说明这一段可以贡献 2 个有效子串。我们只需要把所有相邻数字的最小值加起来即可。
具体代码
func countBinarySubstrings(s string) int {
if len(s) <= 1 {
return 0
}
ans := 0
prevLen := 0
currLen := 1
for i := 1; i < len(s); i++ {
// 如果当前字符和前一个字符相同,当前组的长度加 1
if s[i] == s[i-1] {
currLen++
} else {
// 如果不同,说明遇到边界了(比如从 0 变成了 1)
// 此时上一组和当前组的最小长度,就是它们能组成的有效子串数量
if prevLen < currLen {
ans += prevLen
} else {
ans += currLen
}
// 滚动更新:当前组变成上一组,新的一组长度初始化为 1
prevLen = currLen
currLen = 1
}
}
// 遍历结束后,不要忘记加上最后两组计算出的有效子串
if prevLen < currLen {
ans += prevLen
} else {
ans += currLen
}
return ans
}