题目描述
思路解析动画文字版
暴力法的关键动作是“等右边第一个不更贵的价格”。单调栈会把这些还没等到折扣的下标先存起来。
不变量:栈里的下标都还没拿到折扣;从左到右扫描保证当前 price 是它们遇到的第一个右侧候选。
初始化:答案先复制原价:如果某件商品最后没有折扣,答案就保持原价。所以先复制一份 prices 到 ans。
i=0,price=8:没有旧商品可结算:第一件商品 8 还没有看到右边的商品,先把下标 0 压栈,等后面来一个 <=8 的价格。
i=1,price=4:8 等到了折扣:当前 4 是下标 0 右边遇到的第一个 <=8 的价格,所以 8 的折扣立刻确定,不能继续往后找 2。
i=1,price=4:自己也要等待折扣:结算完别人后,当前商品 4 也可能在右边找到折扣,所以把下标 1 压入栈。
i=2,price=6:不能给 4 打折:价格 6 比栈顶 4 贵,不能作为 4 的折扣。它自己还没找到折扣,所以压栈等待。
i=3,price=2:先结算 6:当前 2 是下标 2 右侧第一个 <=6 的价格,所以商品 2 的最终价格变成 4。
price=2 还可以继续结算 4:一个当前价格可能连续给多个旧商品打折。因为 2 也是下标 1 右侧第一个 <=4 的价格,所以继续弹栈结算。
i=3,price=2:压栈等待:2 已经帮别人结算完,但它自己仍要等右边有没有 <=2 的价格。
i=4,price=3:不能给 2 打折:3 比 2 贵,不能作为商品 3 的折扣。把 4:3 压栈后,扫描就结束了。
扫描结束:栈里剩下的没有折扣:栈里剩下的下标一直没等到右侧 <= 自己的价格,答案保持原价。最终返回 [4,2,4,2,3]。
单调栈不是背模板,而是把“未来第一个满足条件的人”变成“当前元素解决栈顶的一批等待者”。
参考代码
class Solution: def finalPrices(self, prices): ans = prices[:] stack = [] for i, price in enumerate(prices): while stack and prices[stack[-1]] >= price: ans[stack.pop()] -= price stack.append(i) return ans复杂度
- 时间复杂度:O(n),每个下标最多入栈一次、出栈一次。
- 空间复杂度:O(n),最坏情况下价格递增,所有下标都会留在栈中。
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
简化路径
LeetCode 71 · 中等 · 沿着 栈套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题