SWE · Coding

TikTok 子数组和计数:前缀频次与严格正数的 O(1) 空间追问

先处理负数、零和重复前缀,再证明严格正数为什么允许双指针,区分计数与只判断存在。

题目重点:先处理负数、零和重复前缀,再证明严格正数为什么允许双指针,区分计数与只判断存在。

题意与假设

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

  • 连续且非空,返回下标区间数量;相同数值但不同下标仍各算一次。
  • 一般输入为整数,可含负数和零;正数追问必须每项严格大于零。
  • 本文 target 任意整数、空输入返回零。存在性版可在第一次命中时返回,但计数版不能提前结束。

解题路线

  1. 定义 P[j] 为前 j 个数之和,区间 [i,j) 满足 P[j]−P[i]=target。固定右端点 j,只需知道此前有多少 P[i]=P[j]−target,不能只记是否出现。
  2. 频次表初始 {0:1},代表区间从数组开头开始的空前缀。每读一个值先累加 prefix,再查 prefix−target 的旧频次,最后插入当前 prefix;若先插入,target=0 时会误计空区间。
  3. 负数会让窗口扩展时总和减小,无法按“太大就缩”单调推进。即使没有负数但允许零,同一右端点也可能有多个合法起点,最简单的一次命中计数法仍不成立。
  4. 严格正数且 target>0 时,固定右端点的起点向右移,总和严格下降,因此最多一个合法起点。扩大右端点,超 target 时不断左移;左右指针各最多移动 n 次。
  5. target≤0 且输入严格正数,非空区间不可能命中,直接返回零;先验证输入,不能把不满足正数合同的数组静默当作合法。若允许零,可用“和≤target”计数差求精确和,但那是另一合同。
  6. 在线流的一般版只需当前前缀和与历史频次,不需完整数组。正数版若窗口元素要出队,从无法回读的流里删除最旧值需要保存窗口,数组版 O(1) 空间不自动适用于不可回放流。

手动走一遍

[1,-1,1]、target=1:第三个右端点查到两个值为0的旧前缀,总计3个区间。严格正数 [1,2,1,2]、target=3 得3个区间。若输入 [0,0]、target=0,一共3个非空区间,说明只在每个右端点加1会少算。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Nonempty contiguous subarrays; positive-only follow-up rejects zero/negatives."""
from collections import defaultdict


def count_subarrays(values, target):
    seen = defaultdict(int)
    seen[0] = 1
    prefix = answer = 0
    for value in values:
        prefix += value
        answer += seen[prefix - target]
        seen[prefix] += 1
    return answer


def count_positive_subarrays(values, target):
    if any(value <= 0 for value in values):
        raise ValueError("strictly positive values required")
    if target <= 0:
        return 0
    left = total = answer = 0
    for right, value in enumerate(values):
        total += value
        while left <= right and total > target:
            total -= values[left]
            left += 1
        if total == target:
            answer += 1
    return answer

复杂度

哈希频次版期望时间 O(n)、空间 O(n),答案最多 n(n+1)/2,要用足够宽的整数。严格正数数组版 O(n) 时间、O(1) 额外空间;不包括调用者持有的数组。

易错点与追问

容易失分的边界

  • 频次表退化为集合会漏重复前缀。
  • 先插入当前前缀会计入空区间。
  • 零被误当作严格正数。
  • 正数数组的空间结论被照搬到流。

继续深挖

  • 如何只返回存在性或一组下标?
  • 若需要列出所有区间,输出量会怎样变化?
  • 含零的非负数组如何用两个 at-most 计数?
  • target 与前缀和超过32位怎样处理?
✓已复制微信号!