LeetCode 962. 最大宽度坡
题目描述

题意分析
找到两个下标
i < j,满足nums[i] <= nums[j],并最大化宽度j - i。只限制两个端点的值,中间元素的大小不影响是否合法。不存在这样的下标对时返回 0。
解法:候选单调栈 + 反向扫描
核心思路
[!blue]
先筛选可能成为最优左端的位置。若在位置
i之前已经存在p,满足nums[p] <= nums[i],那么任何能与i配对的右端也能与p配对,而且p更早、宽度更大。因此i可以舍弃,只有新的前缀最小值需要保留。从左向右把这些下标压栈,栈内下标递增,对应数值严格递减。遇到相等值也不入栈,因为更早的同值位置一定更有利。栈顶因此是剩余候选中值最小的一个。
再从右向左枚举右端
j。若nums[j] >= nums[栈顶],当前j就是这个候选能遇到的最右匹配位置,计算宽度后可以永久弹出;今后的右端只会更靠左,不可能让这个候选取得更大宽度。同一个右端可能让多个候选达到各自最大宽度,因此要持续弹栈。若栈顶都不能匹配,栈中更早候选的值更大,也都不能匹配当前右端,可以直接换下一个
j。这样既保留了所有可能最优的左端,又为每个候选找到最远右端。
解题步骤
- 正向扫描数组;栈为空,或当前值严格小于栈顶值时,将当前下标入栈。
- 将答案初始化为 0,从末尾向前枚举
j。- 只要栈非空且栈顶值不大于
nums[j],就用j - 栈顶下标更新答案,并弹出这个候选。- 处理完所有右端后返回最大宽度。
若某候选在右侧没有合法伙伴,它最迟会在扫描到自身时以宽度 0 被弹出;这个值不会增加答案,因此无需把自身当作一个合法坡计数。
代码实现
class Solution {
public int maxWidthRamp(int[] nums) {
int n = nums.length;
int[] stack = new int[n];
int top = -1;
// 只需保留新前缀最小值,其余左端被更早且不大的位置支配。
for (int i = 0; i < n; i++) {
if (top == -1 || nums[i] < nums[stack[top]]) {
stack[++top] = i;
}
}
int answer = 0;
// 从右往左,保证第一次匹配到的就是该左端点的最右伙伴。
for (int j = n - 1; j >= 0; j--) {
while (top >= 0 && nums[j] >= nums[stack[top]]) {
answer = Math.max(answer, j - stack[top]);
top--;
}
}
return answer;
}
}
func maxWidthRamp(nums []int) int {
n := len(nums)
stack := make([]int, 0, n)
// 只需保留新前缀最小值,其余左端被更早且不大的位置支配。
for i := 0; i < n; i++ {
if len(stack) == 0 || nums[i] < nums[stack[len(stack)-1]] {
stack = append(stack, i)
}
}
answer := 0
// 从右往左,保证第一次匹配到的就是该左端点的最右伙伴。
for j := n - 1; j >= 0; j-- {
for len(stack) > 0 && nums[j] >= nums[stack[len(stack)-1]] {
i := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if j-i > answer {
answer = j - i
}
}
}
return answer
}
复杂度分析
- 时间复杂度:$O(n)$。两次扫描各经过数组一次;内层虽然使用循环,但每个下标至多入栈和出栈一次,弹栈总次数也是线性的。
- 空间复杂度:$O(n)$,严格递减数组会使所有下标都成为候选。
关键点总结
[!green]
- 更早且值不大的位置,在所有可能右端下都优于较晚位置,因此只保留前缀新最小值。
- 从右向左扫描,使候选首次匹配时就能确定它的最大宽度。
- 栈顶值最小;它都不满足时,栈内其余候选也不满足当前右端。
易错点总结
[!yellow]
- 栈必须存下标,单独存值无法计算宽度。
- 不能正向扫描右端后立即弹出候选,那时未来还可能出现更远的匹配。
- 内层只弹一个候选,会漏掉同一右端对其他候选提供的最远匹配。
- 匹配允许值相等,条件必须包含等号。
- 宽度是
j - i,不是包含两端的元素个数j - i + 1。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1124. 表现良好的最长时间段 | 中等 | 同样先保存单调下降的左端候选,再从右向左匹配以最大化下标差,比较条件有所不同。 |
| 121. 买卖股票的最佳时机 | 简单 | 同样只允许左位置早于右位置,但本题最大化距离而非数值差,需要保留合适的左端候选。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!