
本文介绍一种时间复杂度为 o(n) 的贪心策略,通过识别数组中“破坏单调性”的关键下降位置(即 a[i] ≤ a[i−1]),统计需执行的最少段增操作次数,而非累加数值差。
本文介绍一种时间复杂度为 o(n) 的贪心策略,通过识别数组中“破坏单调性”的关键下降位置(即 a[i] ≤ a[i−1]),统计需执行的最少段增操作次数,而非累加数值差。
在解决“使数组严格递增所需的最少段增操作数”问题时,一个常见误区是将「操作次数」与「增量总和」混淆。题目明确要求:每次操作可选择任意连续子数组,并将其所有元素增加同一个正整数;目标是最小化操作次数(而非增量大小之和)。因此,核心观察在于:
✅ 一次段增操作可修复后续所有因当前元素过小导致的递增断裂;
❌ 不应为每个不满足 A[i] > A[i−1] 的位置单独执行多次操作,更不应将“需补的差值”计入操作次数。
关键洞察:操作次数 = 下降点数量
考虑严格递增的必要条件:对所有 i ≥ 1,必须有 A[i] > A[i−1]。若该条件在位置 i 失败(即 A[i] ≤ A[i−1]),则至少需要一次操作来提升 A[i] 或其左侧某段——但最优策略是:从该下降点开始,向右延伸一段进行统一提升,从而一劳永逸地避免后续重复修复。
更重要的是:无论你如何选择段长和增量值,每一次“从左到右扫描中首次出现 A[i] ≤ A[i−1]”都必然对应一次不可省略的操作。原因在于:
- 此前前缀 A[0..i−1] 已严格递增;
- A[i] 不大于前驱,说明它“拖了后腿”,而任何覆盖 A[i] 的段增操作都无法改变 A[0..i−1] 的相对关系;
- 因此,必须至少发起一次新操作来抬高包含 A[i] 的某段(最简选择就是从 i 开始向右)。
由此得出简洁解法:
遍历数组,统计满足 A[i]
正确实现代码
def solution(A):
if len(A) <= 1:
return 0
moves = 0
for i in range(1, len(A)):
if A[i] <= A[i-1]:
moves += 1
return moves
# 测试用例验证
print(solution([4, 2, 4, 1, 3, 5])) # 输出: 2 ✅
print(solution([3, 5, 7, 7])) # 输出: 1 ✅
print(solution([1, 5, 6, 10])) # 输出: 0 ✅
print(solution([2, 1])) # 输出: 1 ✅
print(solution([1, 1, 2])) # 输出: 1 ✅(注意:不是2!)为什么原代码错误?
原始实现错误地将 moves += diff(即 max_so_far + 1 - current_val)当作操作次数:
- 它实际计算的是最小总增量和,而非操作次数;
- 例如 [2, 1] 中,diff = 2,但只需1次操作(如对 [1] 加 2,或对整个 [2,1] 加 2 → [4,3],再微调?不,更优是仅对第2个元素增2 → [2,3],但按题意允许任意段,所以直接对 [1] 增2即可,1次);
- 更严重的是,它隐含假设每次只能改单个元素(违背“段操作”前提),且未利用段操作可批量修复的能力。
注意事项与边界处理
- ✅ 数组长度为 0 或 1 时,天然满足严格递增,返回 0;
- ✅ 比较使用 A[i-1],等价于 A[i]
- ⚠️ 本算法不关心具体增量值,也不需模拟修改后数组——这是问题抽象带来的效率飞跃;
- ⚠️ 时间复杂度 O(n),空间复杂度 O(1),完美适配 N ≤ 10⁵ 的约束。
总结
该问题的本质不是数值调整,而是定位结构性缺陷。每一个 A[i] ≤ A[i−1] 都是一个“不可绕过的修复触发点”,而一次段增操作足以覆盖从此处开始的所有后续潜在问题。摒弃对“加多少”的执念,转而聚焦“在哪一刻必须动手”,是通向最优解的关键跃迁。










