题目重点:每个右端点只统计可见的同值左端点,严格递减栈保留跨过较小元素后的可选次数。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 整数数组可含负数和重复值;单元素子数组满足条件。答案可能 O(N²),语言需支持足够宽整数。
解题路线
- 枚举右端点值 x。合法左端点必须也是 x,且两端之间不能出现 >x;小于 x 的元素不妨碍。
- 维护值严格递减的栈,栈项 (value,count) 表示从最近一个更大值之后可选的同值起点数。遇到 x 时弹掉所有小于 x 的栈项;若顶项等于 x,count+1,否则压入 (x,1)。该 count 正是以当前位置结尾的新合法子数组数。
- 每个元素至多入栈、出栈一次,累计 count。相等值必须合并,不能被 `<` 弹掉;遇到更大值时旧小值无法跨过它再形成合法区间。
手动走一遍
[3,1,3]:第一个 3 贡献 1,1 贡献 1,末尾 3 弹出 1 后与前 3 合并,贡献 2(单独末尾和整个 [3,1,3]),总数 4。若中间是 4,则首尾 3 之间被更大值阻断。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
def count_endpoint_max_subarrays(nums: list[int]) -> int:
"""Count nonempty subarrays with first == last == maximum."""
stack = [] # Strictly decreasing values; count of equal values since last larger.
answer = 0
for value in nums:
while stack and stack[-1][0] < value:
stack.pop()
if stack and stack[-1][0] == value:
stack[-1] = (value, stack[-1][1] + 1)
else:
stack.append((value, 1))
answer += stack[-1][1]
return answer
复杂度
每个元素最多入栈、出栈一次,时间 O(N)、栈 O(N)。暴力枚举区间并维护最大值也能 O(N²) 作对照;文章训练代码经 20,000 组随机数组与该暴力 oracle 比对。
易错点与追问
容易失分的边界
- 只数相邻相等元素,漏掉中间较小值。
- 遇到相等值也弹栈,丢掉合法左端点。
- 遇到更大值不清空被阻断的较小值。
- 把空数组答案当 1。
- 结果可能超过 32 位。
继续深挖
- 如何给出每个右端点的贡献而非只求总数?
- 若条件变成 first=last=最小值,栈方向如何反转?
- 流式输入时能否在线更新答案?
- 如果要求只统计长度≥2,怎样调整贡献?
