题目重点:反复取最左非零值作为pivot,只向右消减连续不小于pivot的元素,累计每轮pivot。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 数组由非负整数构成;每轮在第一个严格小于pivot的元素处立即停止,不跳过它,也不对其右侧继续。返回所有pivot之和。
- 输入复制后处理,不修改调用方数组。约束未给,代码采用与题意一一对应的可验证基线,不宣称已有线性最优解。
解题路线
- 找最左非零位置left,取pivot=a[left]>0。向右每个满足a[j]>=pivot的元素减pivot;遇更小值终止本轮,answer加pivot。
- 每轮首个非零一定被减到零,后续处理永远从它右侧开始;left可单调前进不必每次重新从0扫描。最多n个非零消减轮,零位直接略过。
- 虽然轮数O(n),每轮可扫很长后缀,因此最坏O(n²),例如严格递增数组会反复扫描后缀。若大规模要求更快,可考虑支持区间min、lazy range subtract与firstBelow查询的线段树;先证明范围选择,再实现验证。
- 不能用熟悉的相邻正差sum直接替代,当前pivot会在较低高度处被截断,且后续pivot会改变更右侧余额,需对模型重新证明。
手动走一遍
[3,1,2]:pivot3只减首元素→[0,1,2];pivot1减后两项→[0,0,1];pivot1再减末项,总5。[2,3,1,4]依次pivot2/1/3,总6,而简单按原值局部增量可能给错。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
def subtraction_total(values):
"""Literal training contract. Stop a round at first element strictly below pivot."""
if any(not isinstance(v, int) or v < 0 for v in values):
raise ValueError("nonnegative integer array required")
a = list(values)
left = 0
answer = 0
while left < len(a):
if a[left] == 0:
left += 1
continue
pivot = a[left]
right = left
while right < len(a) and a[right] >= pivot:
a[right] -= pivot
right += 1
answer += pivot
left += 1 # This position became zero; later operations start to its right.
return answer
复杂度
最坏O(n²)时间、O(n)输入副本空间,额外计数O(1)。线段树扩展可每轮O(log n)查询与区间减,最多n轮,目标O(n log n),需实现数据结构后再核证而非假定基线已达此界。
易错点与追问
容易失分的边界
- 遇到零也必须停止当前正pivot,不能继续跨过它。
- 负数输入会破坏非零pivot为正与终止性,训练接口明确拒绝。
继续深挖
- 返回每轮pivot序列只需append,输出最多n;不能从总和还原唯一操作序列。
- 如果每轮遇更小值不是停止而是跳过,操作范围变不连续,原算法与复杂度证明都需调整。
