题目重点:从 [1,1,3,4]→[4,2,1] 推导切钢条各轮计数;对比逐轮模拟与排序分组,覆盖重复长度、空输入和复杂度。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 钢条长度是正整数;每轮开始先记录仍存在的钢条根数。
- 一轮减去当前最短长度,减到 0 的钢条同时移除;空数组返回空列表。
- 题目没有规定非正长度的行为,训练实现显式拒绝它们,而非凭空定义切割规则。
解题路线
- 关键不变量:所有仍在场的长度同时减去同一个量,任意两根钢条的长短顺序不变;同原始长度的钢条在同一轮归零。
- 排序原始长度。每遇到一个新的不同长度,就开始新一轮;该位置 i 及其右边共 n−i 根钢条仍在场,直接记录 n−i。
- 无需真的按每轮切割。若长度值域小且已知,可用频次数组取代排序;值域未给时通用排序更稳。
手动走一遍
[1,1,3,4] 排序后仍是 [1,1,3,4]。新长度出现在下标 0、2、3,分别记录 4、2、1。第一轮切 1 后剩原始长度 3、4;第二轮切 2 后仅剩原始长度 4;第三轮结束。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from collections.abc import Sequence
def remaining_counts(lengths: Sequence[int]) -> list[int]:
"""Counts at the start of each cut round for positive rod lengths."""
if any(length <= 0 for length in lengths):
raise ValueError("rod lengths must be positive")
ordered = sorted(lengths)
return [len(ordered) - i for i, value in enumerate(ordered)
if i == 0 or ordered[i - 1] != value]
if __name__ == "__main__":
assert remaining_counts([1, 1, 3, 4]) == [4, 2, 1]
复杂度
排序 O(n log n) 时间,输出扫描 O(n);代码 sorted 额外 O(n) 空间,结果长度 d 等于不同长度的个数。若输入已排序,可只扫描并把额外工作空间降到 O(d)。
易错点与追问
容易失分的边界
- 把同长度的两根钢条当成两轮,错误输出 [4,3,2,1]。
- 每轮只移除最短钢条却忘记其他钢条也同步缩短。
- 把记录时间放到切完之后,样例会少掉首轮的 4。
继续深挖
- 若数据量大但长度值域为 1..M,计数排序与后缀频次可做到 O(n+M);何时值得用?
- 如果需要返回每轮切除长度,可输出相邻不同原始长度的差。
- 流式到达钢条且频繁查询所有轮结果时,如何维护不同长度的有序频次?
