|
| 1 | +# 8.3 Daily Temperatures |
| 2 | + |
| 3 | +> **Source:** [`src/main/kotlin/stack/DailyTemperatures.kt`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/blob/main/src/main/kotlin/stack/DailyTemperatures.kt) |
| 4 | +> **Pattern:** monotonic stack · **Core page** |
| 5 | +
|
| 6 | +## The Problem |
| 7 | + |
| 8 | +Given an array `temperatures` (daily highs), return an array `answer` where `answer[i]` is the number of days you must wait until a **warmer** day — or `0` if none ever comes. |
| 9 | + |
| 10 | +- Constraints: $1 \le n \le 10^5$; $30 \le temperatures[i] \le 100$. |
| 11 | + |
| 12 | +## Examples |
| 13 | + |
| 14 | +``` |
| 15 | +Input: temperatures = [73,74,75,71,69,72,76,73] |
| 16 | +Output: [1,1,4,2,1,1,0,0] |
| 17 | + 73 -> 74 (1 day), 75 -> 76 (4 days), 71 -> 72 (1 day)... last two never warmer |
| 18 | +
|
| 19 | +Input: temperatures = [30,40,50,60] |
| 20 | +Output: [1,1,1,0] |
| 21 | +``` |
| 22 | + |
| 23 | +## Intuition — "who am I waiting for?" is a stack of unresolved days |
| 24 | + |
| 25 | +The naive double loop (for each day, scan forward for the first warmer) is $O(n^2)$. The monotonic-stack insight: **a day only stays unresolved while the following days keep getting colder.** The moment a warmer day arrives, *all* unresolved colder days that came before it get resolved at once — each by this same day. |
| 26 | + |
| 27 | +So maintain a stack of **indices of unresolved days**, in *decreasing temperature order* (the stack top is the coldest among them). For each new day `i`: |
| 28 | + |
| 29 | +- while the stack is non-empty and `temperatures[i] > temperatures[stack.top]` — day `i` is the first warmer day for the top: resolve it (`answer[top] = i - top`), pop; |
| 30 | +- then push `i` (unresolved, for now). |
| 31 | + |
| 32 | +The "while" is the resolve-all-the-cold-ones sweep. Each index is pushed once and popped once, so the whole run is $O(n)$ — the amortization argument from the [primer](pattern-primer.md) in action. |
| 33 | + |
| 34 | +**Why indices, not temperatures?** The answer needs *distance* (`i - top`). Storing temperatures would force a parallel array of positions; storing indices gives both the value (`temperatures[top]`) and the position. This is the same "store indices" lesson as [8.5](largest-rectangle-in-histogram.md). |
| 35 | + |
| 36 | +**What stays on the stack?** The unresolved *decreasing* tail — days that have no warmer day to their right yet. When the sweep ends, everything still on the stack gets `0` (the `IntArray` default) — no warmer day ever comes. |
| 37 | + |
| 38 | +## Approach 1 — Nested scan (too slow) |
| 39 | + |
| 40 | +For each day, scan right until a warmer day: worst case $O(n^2)$ (a strictly decreasing array scans $n, n-1, \ldots, 1$). |
| 41 | + |
| 42 | +## Approach 2 — Monotonic stack (the repo's version, optimal) |
| 43 | + |
| 44 | +```kotlin |
| 45 | +class DailyTemperatures { |
| 46 | + /** |
| 47 | + * @param temperatures daily temperatures |
| 48 | + * @return days until a warmer day, 0 if none |
| 49 | + */ |
| 50 | + fun dailyTemperatures(temperatures: IntArray): IntArray { |
| 51 | + val result = IntArray(temperatures.size) { 0 } |
| 52 | + val stack = mutableListOf<Int>() // indices of unresolved days, decreasing temps |
| 53 | + |
| 54 | + for (i in temperatures.indices) { |
| 55 | + // Day i resolves every unresolved colder day above it on the stack |
| 56 | + while (stack.isNotEmpty() && temperatures[i] > temperatures[stack.last()]) { |
| 57 | + val idx = stack.removeLast() |
| 58 | + result[idx] = i - idx // first warmer day is i |
| 59 | + } |
| 60 | + stack.add(i) // i stays unresolved (for now) |
| 61 | + } |
| 62 | + return result // stack leftovers already 0 |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +```java |
| 68 | +import java.util.*; |
| 69 | + |
| 70 | +public class DailyTemperatures { |
| 71 | + /** |
| 72 | + * @param temperatures daily temperatures |
| 73 | + * @return days until a warmer day, 0 if none |
| 74 | + */ |
| 75 | + public int[] dailyTemperatures(int[] temperatures) { |
| 76 | + int[] result = new int[temperatures.length]; |
| 77 | + Deque<Integer> stack = new ArrayDeque<>(); // indices of unresolved days |
| 78 | + |
| 79 | + for (int i = 0; i < temperatures.length; i++) { |
| 80 | + while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { |
| 81 | + int idx = stack.pop(); |
| 82 | + result[idx] = i - idx; // first warmer day is i |
| 83 | + } |
| 84 | + stack.push(i); |
| 85 | + } |
| 86 | + return result; // leftovers already 0 |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +```cpp |
| 92 | +#include <stack> |
| 93 | +#include <vector> |
| 94 | + |
| 95 | +class DailyTemperatures { |
| 96 | +public: |
| 97 | + /** |
| 98 | + * @param temperatures daily temperatures |
| 99 | + * @return days until a warmer day, 0 if none |
| 100 | + */ |
| 101 | + std::vector<int> dailyTemperatures(std::vector<int>& temperatures) { |
| 102 | + std::vector<int> result(temperatures.size(), 0); |
| 103 | + std::stack<int> st; // indices of unresolved days |
| 104 | + |
| 105 | + for (int i = 0; i < (int)temperatures.size(); i++) { |
| 106 | + while (!st.empty() && temperatures[i] > temperatures[st.top()]) { |
| 107 | + int idx = st.top(); st.pop(); |
| 108 | + result[idx] = i - idx; // first warmer day is i |
| 109 | + } |
| 110 | + st.push(i); |
| 111 | + } |
| 112 | + return result; // leftovers already 0 |
| 113 | + } |
| 114 | +}; |
| 115 | +``` |
| 116 | + |
| 117 | +```python |
| 118 | +def daily_temperatures(temperatures: list[int]) -> list[int]: |
| 119 | + """ |
| 120 | + @param temperatures: daily temperatures |
| 121 | + @return: days until a warmer day, 0 if none |
| 122 | + """ |
| 123 | + result = [0] * len(temperatures) |
| 124 | + stack = [] # indices of unresolved days |
| 125 | + |
| 126 | + for i, temp in enumerate(temperatures): |
| 127 | + while stack and temp > temperatures[stack[-1]]: |
| 128 | + idx = stack.pop() |
| 129 | + result[idx] = i - idx # first warmer day is i |
| 130 | + stack.append(i) |
| 131 | + return result |
| 132 | +``` |
| 133 | + |
| 134 | +```rust |
| 135 | +impl Solution { |
| 136 | + /// @param temperatures daily temperatures |
| 137 | + /// @return days until a warmer day, 0 if none |
| 138 | + pub fn daily_temperatures(temperatures: Vec<i32>) -> Vec<i32> { |
| 139 | + let mut result = vec![0; temperatures.len()]; |
| 140 | + let mut stack: Vec<usize> = Vec::new(); // indices of unresolved days |
| 141 | + |
| 142 | + for i in 0..temperatures.len() { |
| 143 | + while let Some(&idx) = stack.last() { |
| 144 | + if temperatures[i] <= temperatures[idx] { break; } |
| 145 | + result[idx] = (i - idx) as i32; // first warmer day is i |
| 146 | + stack.pop(); |
| 147 | + } |
| 148 | + stack.push(i); |
| 149 | + } |
| 150 | + result |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +## Dry run |
| 156 | + |
| 157 | +**Input:** `temperatures = [73,74,75,71,69,72,76,73]`. |
| 158 | + |
| 159 | +``` |
| 160 | +i=0 (73): stack empty -> push 0. stack=[0] |
| 161 | +i=1 (74): 74 > 73 -> resolve 0: result[0]=1, pop. stack=[] |
| 162 | + push 1. stack=[1] |
| 163 | +i=2 (75): 75 > 74 -> result[1]=1, pop. stack=[] |
| 164 | + push 2. stack=[2] |
| 165 | +i=3 (71): 71 > 75? no -> push 3. stack=[2,3] |
| 166 | +i=4 (69): 69 > 71? no -> push 4. stack=[2,3,4] |
| 167 | +i=5 (72): 72 > 69 -> result[4]=1, pop. |
| 168 | + 72 > 71 -> result[3]=2, pop. stack=[2] |
| 169 | + 72 > 75? no. push 5. stack=[2,5] |
| 170 | +i=6 (76): 76 > 72 -> result[5]=1, pop. |
| 171 | + 76 > 75 -> result[2]=4, pop. stack=[] |
| 172 | + push 6. stack=[6] |
| 173 | +i=7 (73): 73 > 76? no -> push 7. stack=[6,7] |
| 174 | +end: stack leftovers {6,7} keep result 0. |
| 175 | +
|
| 176 | +result = [1,1,4,2,1,1,0,0] ✓ |
| 177 | +``` |
| 178 | + |
| 179 | +The pivotal moment is i=5: one day (`72`) resolves *two* unresolved colder days at once (69 at distance 1, 71 at distance 2). That's the monotonic-stack efficiency — each element resolved exactly once, by exactly one later day. |
| 180 | + |
| 181 | +## Complexity |
| 182 | + |
| 183 | +**Time.** Each index pushed once, popped once: |
| 184 | + |
| 185 | +$$ |
| 186 | +T(n) = O(n) |
| 187 | +$$ |
| 188 | + |
| 189 | +**Space.** The stack of unresolved indices: |
| 190 | + |
| 191 | +$$ |
| 192 | +S(n) = O(n) |
| 193 | +$$ |
| 194 | + |
| 195 | +## Variants & follow-ups |
| 196 | + |
| 197 | +- **Next Greater Element I / II** ([8.4](next-greater-element-ii.md)) — same engine, values instead of distances; the circular version doubles the array conceptually. |
| 198 | +- **Online Stock Span** (`src/main/kotlin/stack/OnlineStockSpan.kt`) — the "next greater to the left" mirror: the span is the distance to the *previous* larger element. |
| 199 | +- **Sum Of Subarray Minimums** (`src/main/kotlin/stack/SumOfSubArrayMinimum.kt`) — the deepest descendant: every subarray's minimum is found by this same monotonic stack, then summed by a counting argument. |
| 200 | +- **Interview follow-up:** "Why does the stack stay decreasing?" A day only stays unresolved if every later day so far is colder — so unresolved indices form a decreasing temperature sequence. The `while` pop preserves that invariant, which is what makes "the first warmer day" computable in one sweep. |
0 commit comments