Graduate Mobile Engineer I · OA

Uber OA LC696:相邻游程长度为什么取 min

按标准题训练连续且分组的二进制子串,证明每个0/1边界贡献min(左游程,右游程),避免混为子序列计数。

题目重点:按标准题训练连续且分组的二进制子串,证明每个0/1边界贡献min(左游程,右游程),避免混为子序列计数。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 标准训练合同:统计连续子串,其中0/1数量相等,且所有0连续、所有1连续;不同下标出现计不同子串。
  • 输入仅0/1,空串返回0;题目截图中的规模或其他变体待核。
  • 两个分组顺序可为0后1或1后0,不能只计01。

解题路线

  1. 将连续相同字符视为一个run,长度依次为g1,g2,…。合法子串恰好跨一个字符变化边界,否则其中至少一种字符不连续。
  2. 一个左右run长度a,b的边界能取左右各t个字符,t从1至min(a,b)。每个t对应唯一跨边界的连续窗口,贡献min(a,b)。
  3. 各边界子串互不重复,因此答案Σmin(g_i,g_{i+1})。不需要真的存全部run,只保留previous、current及上一个字符。
  4. 读到新字符时将已完成的相邻run贡献加入答案,再推进previous/current;结束后补最后一对。只在字符变化时结算,避免把未完成run重复计算。
  5. 若题目改为任意子序列或只要求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,怎样用前缀和计数?
  • 返回所有合法窗口时输出量上界与复杂度是什么?
  • 若每组允许三种字符,原证明哪一步改变?
✓已复制微信号!