题目描述
给每天温度,求每天还要等几天才能遇到更高温(没有就填 0)。
思路解析
对每一天都从它往后一格格扫,直到撞见更高温——最坏每天都扫到末尾,n 天就是 n² 次比较。重复劳动很多:很多天会被反复扫过。
关键招是用一个栈记住「还没等到更暖天」的下标,栈里温度底高顶低。新一天温度若大于栈顶那天,就说明栈顶等到了——弹出并用「今天下标 − 它的下标」算天数差;一直弹到栈顶比今天高,再把今天压栈。为什么能这样:一旦今天比栈顶高,栈顶这天的答案立刻定死,再不用回头看。
i=0 · 73 入栈:第 0 天 73,栈是空的,直接压入下标 0,让它开始「等更暖的一天」。
i=1 · 74 大于 73 → 结算:74 比栈顶那天(73)高,第 0 天等到了:ans[0] = 1 − 0 = 1。弹出 0,再压入今天 1。
i=2 · 75 大于 74 → 结算:75 又比栈顶(74)高,第 1 天等到了:ans[1] = 2 − 1 = 1。弹出 1,压入今天 2。
i=3 · 71 小于 75 → 不结算:负例分支:71 比栈顶(75)矮,第 2 天还得继续等,什么都不结算,今天也开始等,压入 3。栈里现在两天 [2, 3],温度仍是底高顶低。
i=4 · 69 小于 71 → 不结算:69 比栈顶(71)还矮,照样不结算,压入 4。栈攒到 [2, 3, 4],温度 75 大于 71 大于 69,严格递减。
i=5 · 72 连弹两天:核心一步:72 一来,连续弹两天——先结算第 4 天 ans[4]=1,再结算第 3 天 ans[3]=2;直到栈顶第 2 天的 75 大于 72,停止弹出,压入今天 5。一个高温一次清掉好几笔账。
i=6 · 76 再连弹两天:76 是个大高温:先弹第 5 天 ans[5]=1,再弹第 2 天 ans[2]=4(等了整整 4 天),栈被清空。压入 6。
i=7 · 73 小于 76 → 不结算:最后一天 73 比栈顶(76)矮,不结算,压入 7。扫描到此结束。
收尾 · 栈里剩 [6,7]:扫描结束,栈里还剩第 6、7 天没等到更暖的,它们的答案保持初始 0。最终 [1,1,4,2,1,1,0,0]。
凡是「为每个元素找右边第一个更大 / 更小的元素」,都用单调栈:每日温度、下一个更大元素、柱状图最大矩形、接雨水都是这一招的变体。
参考代码(Python)
1ans = [0] * len(temps)
2stack = [] # 存「还在等」的下标(单调递减)
3for i, t in enumerate(temps):
4 while stack and t > temps[stack[-1]]: # 今天比栈顶更暖
5 j = stack.pop() # 第 j 天等到了
6 ans[j] = i - j # 天数差 = 今天 − 那天
7 stack.append(i) # 今天开始排队等更暖
8return ans复杂度分析
- 时间复杂度:O(n) —— 每个下标进栈一次、出栈一次,while 总弹出次数不超过 n
- 空间复杂度:O(n) —— 最坏(温度递减)栈里装下全部 n 个下标
套路模板
记住骨架:栈存下标、新元素打破单调性就弹栈结算、再入栈。把比较的 大于 换成 小于,就从「下一个更大」变成「下一个更小」。
1# 「为每个元素找右边第一个更大 / 更小」都套
2stack = [] # 存还没找到答案的下标
3for i, x in enumerate(arr):
4 while stack and x > arr[stack[-1]]: # 维持递减栈
5 j = stack.pop()
6 ans[j] = i - j # 结算 j(天数差/距离)
7 stack.append(i)易错点
- 错误写法:栈里直接存温度值 temps[i] → 正确写法:栈里存下标 i,比较时再 temps[stack[-1]](答案要的是「等几天」即下标差 i−j,只存温度值就拿不到位置,算不出天数)
- 错误写法:while 写成 if 只弹一次 → 正确写法:用 while 连续弹出(一个高温可能让前面好几天同时等到,必须一次性弹光所有比它矮的栈顶,如 i=5 的 72 连弹第 4、第 3 天)
- 错误写法:比较写成 大于等于(温度相等也弹) → 正确写法:只在严格 大于 时弹出(温度相等不算「更暖」,弹了会把等待天数算少一天)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。