SWE / Hiring Event · OA Coding

Microsoft OA 最短至少 K 种不同值子数组:滑窗频次表

用双指针求包含至少 k 个不同值的最短连续子数组,说明收缩顺序、频次归零与不可达返回。

题目重点:用双指针求包含至少 k 个不同值的最短连续子数组,说明收缩顺序、频次归零与不可达返回。

题意与假设

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

  • subarray 是非空连续区间;k≥1,训练接口对 k≤0 报错。
  • 无法达到 k 种不同值时返回 -1;题目未指定该返回值。
  • 元素可为任意整数,允许重复和负数;只关心不同值数。

解题路线

  1. 右指针逐个加入元素,哈希频次表记录当前窗口每个值的次数;只有次数从 0 变 1 时不同值数增加。
  2. 窗口达到至少 k 种后,先记录当前长度,再从左侧删除元素,持续缩到不再满足条件。这样枚举了每个右端点下所有可能的有效更短窗口。
  3. 某元素频次降到 0 时必须从表中删键;以表的键数代表不同值数才正确。
  4. 每个元素最多入窗一次、出窗一次,因此线性。

手动走一遍

[1,2,1,3]、k=3:右端到 3 时窗口 [1,2,1,3] 有 3 种,长度 4;移走左侧 1 后 [2,1,3] 仍有 3 种,更新为 3;再移走 2 后只剩 2 种。答案 3。

Python 参考实现

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

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

from collections import defaultdict
from collections.abc import Sequence


def shortest_at_least_k_distinct(values: Sequence[int], k: int) -> int:
    """Minimum nonempty window length with >=k distinct values, else -1."""
    if k < 1:
        raise ValueError("k must be positive for nonempty windows")
    counts: dict[int, int] = defaultdict(int)
    left = 0
    answer = len(values) + 1
    for right, value in enumerate(values):
        counts[value] += 1
        while len(counts) >= k:
            answer = min(answer, right - left + 1)
            outgoing = values[left]
            counts[outgoing] -= 1
            if counts[outgoing] == 0:
                del counts[outgoing]
            left += 1
    return -1 if answer > len(values) else answer


if __name__ == "__main__":
    assert shortest_at_least_k_distinct([1, 2, 1, 3], 3) == 3

复杂度

O(n) 预期时间,哈希表最多保存 d 个不同元素,O(d) 空间;哈希最坏冲突行为取决于语言实现。

易错点与追问

容易失分的边界

  • 把‘至少 k’误当‘恰好 k’,或者只在首次达标时记录一次。
  • 频次减到 0 后仍留在字典,导致 len(counts) 假多。
  • 先删左端再记录,漏掉刚达标的窗口。
  • 无法满足时返回 n+1 这一内部哨兵,而未说明接口契约。

继续深挖

  • 若要求返回所有最短窗口下标,怎样处理并列?
  • 如果要统计恰好 k 种不同值的子数组总数,为什么需要 atMost(k)−atMost(k−1)?
  • 输入是流且要不断查询当前最短历史窗口时,如何维护结果?
✓已复制微信号!