题目重点:用写指针在 O(n) 时间、O(1) 额外空间实现每个值最多出现 K 次,处理 K=0 和数组尾部语义。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 返回有效前缀长度,新内容仅在 values[:length] 中有意义;原列表尾部可保留旧值,不需要实际缩短容器。
- k≥0,负 k 报错;输入可空,值可为任意可比较整数。
解题路线
- write 指向下一个输出位置,依次读每个原值。若 write<k,则当前值总可保留。
- 否则将当前值与输出前缀中倒数第 k 个值比较:若相同,说明已有 k 个该值,应跳过;不同则写入 values[write] 并递增 write。
- 因为输入已排序,同值相邻;不变量是处理前缀后 values[:write] 已按原顺序保留每个值最多 k 个且长度最大。读取下标始终不小于写下标,原地写不会覆盖未来未读值。
手动走一遍
values=[1,1,1,2,2,3]、k=2,依次写入前两个 1、跳过第三个 1,最终有效前缀 [1,1,2,2,3],返回 5。k=0 时返回 0。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Keep at most k copies of each value in a sorted array, in place."""
def keep_at_most_k(values: list[int], k: int) -> int:
if k < 0:
raise ValueError("k must be nonnegative")
if k == 0:
return 0
write = 0
for value in values:
if write < k or value != values[write - k]:
values[write] = value
write += 1
return write
复杂度
每个元素读取一次,O(N) 时间、O(1) 额外空间;若要求物理删除列表尾部,Python 的切片删除会修改容器长度,但题目常以有效前缀长度为接口。
易错点与追问
容易失分的边界
- 把 k 写死为 2,仅解决常见固定变体。
- k=0 时访问 values[write−k],或对空数组越界。
- 误认为需要把尾部也填零或删除,混淆逻辑长度与物理数组长度。
继续深挖
- 如果数组未排序且仍要求保留原相对顺序,每个值最多 k 次,需要什么额外空间?
- 若必须保留每个值最后 k 次而非前 k 次,扫描方向如何变化?
