LeetCode 1475. 商品折扣后的最终价格
题目描述


题意分析
对每件商品,在它右侧寻找第一件价格小于或等于其原价的商品,并用这个价格作为折扣。最终价格为当前原价减去折扣;如果右侧没有符合条件的商品,就保持原价。
“第一件”按下标先后决定,不是寻找右侧最低价。相同价格也能触发折扣,得到零是合法结果。所有判断都使用原始价格,不能让其他商品打折后的新价格参与后续比较。
解法:单调栈寻找右侧首个不大于元素
核心思路
[!blue]
如果逐件向右搜索,很多位置会被反复检查。改为从左向右扫描:读到当前价格时,为左侧仍在等待、且原价不低于它的商品直接确定折扣。用栈保存这些尚未找到答案的商品下标,便于写回各自结果位置。
栈中下标对应的原价从栈底到栈顶严格递增。当前价格不大于栈顶原价时,弹出该下标并计算折后价,再继续判断新的栈顶。当前价格可能同时为多个等待商品提供折扣,所以必须持续弹出,而不是只处理一个。
被弹商品此前一直留在等待栈中,说明从它到现在还没有出现过符合条件的右侧价格。扫描顺序又是从左到右,因此当前商品就是它右侧第一件不更贵的商品,折扣一旦确定就不需要再考虑更远位置。
当栈顶原价已经小于当前价格,栈中更靠下的价格只会更小,同样不符合打折条件,可以停止弹栈。随后把当前下标入栈,等待未来商品来确定它的折扣;弹完再压,也避免当前商品与自己匹配。
答案数组先复制全部原价,只在找到折扣时修改对应结果。扫描完仍在栈中的商品没有任何合适折扣,保留初始原价即可;原始价格数组保持不变,用于后续所有比较。
解题步骤
- 复制原价格数组作为结果,准备保存待处理下标的空栈。
- 从左到右扫描当前商品,只要栈顶原价大于或等于当前原价,就弹出栈顶。
- 为弹出的商品写入“该商品原价减当前原价”,继续处理其他符合条件的栈顶。
- 弹栈结束后将当前下标入栈。
- 扫描完成返回结果,尚未弹出的下标自动保留原价。
代码实现
class Solution {
public int[] finalPrices(int[] prices) {
// 先按「无折扣」初始化,扫描结束后仍在栈里的元素自动保留原价。
int[] res = prices.clone();
// 栈里存下标:结算时既要写 res[idx] 又要读 prices[idx]。
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < prices.length; i++) {
// 栈顶原价不小于当前价时结算折扣,相等价格也符合条件。
while (!stack.isEmpty() && prices[stack.peek()] >= prices[i]) {
int idx = stack.pop();
res[idx] = prices[idx] - prices[i];
}
// 必须弹完再压,否则自己会把自己结算成 0。
stack.push(i);
}
return res;
}
}
func finalPrices(prices []int) []int {
// 先按「无折扣」初始化,扫描结束后仍在栈里的元素自动保留原价。
res := make([]int, len(prices))
copy(res, prices)
// 栈里存下标:结算时既要写 res[idx] 又要读 prices[idx]。
stack := []int{}
for i, price := range prices {
// 栈顶原价不小于当前价时结算折扣,相等价格也符合条件。
for len(stack) > 0 && prices[stack[len(stack)-1]] >= price {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
res[idx] = prices[idx] - price
}
// 必须弹完再压,否则自己会把自己结算成 0。
stack = append(stack, i)
}
return res
}
复杂度分析
- 时间复杂度:
O(n)。每个下标入栈一次、最多出栈一次,全部内层弹栈次数不超过商品数。- 空间复杂度:
O(n)。单调栈最多保存全部下标,结果数组也需要线性空间。
关键点总结
[!green]
- 栈记录尚未遇到合适折扣的位置,“第一个”由等待状态和扫描顺序共同保证。
- 相等原价也满足条件,因此弹栈比较包含等号。
- 栈存下标而不是只有价格,才能把答案写回对应商品。
- 原价与结果分开保存,避免打折后的值污染判定。
易错点总结
[!yellow]
- 寻找右侧最低价:题目只使用第一件符合条件的商品,更远的更低价不应替换它。
- 比较条件写成严格大于:相同价格可以打折,不能漏掉等号。
- 每轮只弹一个下标:当前商品可能同时解决多个等待位置,需使用循环。
- 先把当前下标入栈再匹配:当前值会满足与自身相等的条件,错误地把自己当折扣。
- 用已经打折的结果比较:折扣规则依赖原价,需要保留原始价格。
- 给未找到折扣的位置保留默认零值:无折扣时应该返回原价,结果应先复制原数组。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 496. 下一个更大元素 I | 简单 | 同样找右侧第一个满足大小关系的值,本题要求小于等于,且输出原价减去该值。 |
| 739. 每日温度 | 中等 | 单调栈的待匹配下标结构相同,原题找严格更大并返回距离,本题找不大于的折扣值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!