题目重点:按标准题训练连续且分组的二进制子串,证明每个0/1边界贡献min(左游程,右游程),避免混为子序列计数。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 标准训练合同:统计连续子串,其中0/1数量相等,且所有0连续、所有1连续;不同下标出现计不同子串。
- 输入仅0/1,空串返回0;题目截图中的规模或其他变体待核。
- 两个分组顺序可为0后1或1后0,不能只计01。
解题路线
- 将连续相同字符视为一个run,长度依次为g1,g2,…。合法子串恰好跨一个字符变化边界,否则其中至少一种字符不连续。
- 一个左右run长度a,b的边界能取左右各t个字符,t从1至min(a,b)。每个t对应唯一跨边界的连续窗口,贡献min(a,b)。
- 各边界子串互不重复,因此答案Σmin(g_i,g_{i+1})。不需要真的存全部run,只保留previous、current及上一个字符。
- 读到新字符时将已完成的相邻run贡献加入答案,再推进previous/current;结束后补最后一对。只在字符变化时结算,避免把未完成run重复计算。
- 若题目改为任意子序列或只要求0/1等量但不要求分组,该公式失效,应重新建模。题目的单调栈第二题仅有图片,不能由此编号推断另一题。
手动走一遍
标准训练例00110011的run长度[2,2,2,2],每个边界贡献2,总6。0101有三个边界,每个贡献1;整个0101虽0/1等量,但两种字符均不只一组,不能另算一个。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
def count_binary_substrings(text: str) -> int:
if any(c not in "01" for c in text):
raise ValueError("binary string required")
previous = current = answer = 0
last = None
for char in text:
if char == last:
current += 1
else:
answer += min(previous, current)
previous, current = current, 1
last = char
return answer + min(previous, current)
复杂度
O(n)时间、O(1)辅助空间。独立oracle枚举全部连续子串,直接检查字符数量相等且恰好一次变化;2,047个长度0..10二进制串对照通过。
易错点与追问
容易失分的边界
- 统计任意01/10位置对,混成子序列问题。
- 只检查0/1相等,误计多次变化的串。
- 结尾未加最后一对run。
- 同样文本多次出现只计一次,误去重下标。
继续深挖
- 流式分块输入如何保留跨块run状态?
- 只要求0/1等量而允许多run,怎样用前缀和计数?
- 返回所有合法窗口时输出量上界与复杂度是什么?
- 若每组允许三种字符,原证明哪一步改变?
