Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
For each window size w from 1..n, output the maximum over all length-w windows of that window's minimum. Don't enumerate windows. Instead, for each element find the largest window in which it is the minimum (its span between the previous-smaller and next-smaller elements) using a monotonic stack; that element is a candidate answer for that span length. Take the max per length, then sweep from large lengths down to small so every size inherits the best of any larger feasible span. O(n).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can flip an O(n²)-over-windows problem into an O(n) per-element span computation with a monotonic stack, and reason about the 'a length-L answer also serves all smaller lengths' sweep.
Where candidates slip
Brute-forcing all windows (TLE for n up to 10⁵), getting the span boundaries wrong, or forgetting the descending sweep so smaller window sizes are left too small.
How to stand out
Name the previous-smaller / next-smaller monotonic-stack technique, explain why the largest span an element wins as min answers that length, and justify the descending max-propagation in one sentence.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachExplore the full treeCollapse the tree
How to read it:Monotonic stacks give each element's span; map span→max element; sweep down so smaller sizes inherit.
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Every Window of Every Size
1Hint 1Explore hintHide hint
Directly translate the definition: for each window size w, slide a window across and track its minimum.
2Hint 2Explore hintHide hint
Maintaining the running minimum as the window slides keeps each size at O(n), giving O(n²) overall.
3Hint 3Explore hintHide hint
n up to 10⁵ means n² ≈ 10¹⁰ operations — far too slow. This frames why we need the stack approach.
A · Core idea
The literal reading: for each window size w from 1 to n, examine every length-w window, compute its minimum, and keep the maximum of those minimums.
Why it's the natural first attempt. It mirrors the problem statement one-to-one — two nested loops over (size, start position) plus a min. There's no insight required, just enumeration.
Making the inner min cheap. For a fixed size, sliding the window one step changes only the entering and leaving elements. A monotonic deque tracks the window minimum in amortised O(1), so each size is O(n) and the whole thing is O(n²). Without that, recomputing the min from scratch makes it O(n³).
Why we don't ship it. At n = 10⁵, O(n²) is ~10¹⁰ operations — a guaranteed timeout. It's useful only as a correctness oracle to test the optimal solution against on small inputs.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [2, 6, 1, 12]Array state
size 2 win 0
[2,6] min = 2
[12, 2, 1, 1]C · Code with teaching comments
Reference implementation previewpublic long[] riddleBrute(long[] arr) { int n = arr.length; long[] ans = new long[n]; for (int w = 1; w <= n; w++) { long best = Long.MIN_VALUE;Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Brute force re-derives each window's minimum independently and so repeats enormous work. The optimal flips the question: rather than 'what is each window's min?', ask 'for each element, which windows is it the min of?' — answered once per element with a stack.
ComplexityO(n²) time, O(n) space — n window sizes × O(n) work to scan windows of that size = O(n²). With a naive inner min recomputation it degrades to O(n³).
E · Edge cases and pitfalls5arr = [5] — Only size 1; ans = [5].Explore edge casesHide edge cases
Edge cases handled
arr = [5]Only size 1; ans = [5].arr = [4, 4]size 1 → 4, size 2 → min(4,4)=4 → [4, 4].
Pitfalls
- Recomputing the min from scratch for every window — O(n³), worse than necessary.
- Off-by-one in the start bound: use s + w <= n.
- Believing it will pass — n = 10⁵ guarantees a timeout.
Approach 2 of 2 · Optimal
Monotonic Stack — Span of Dominance (Optimal)
1Hint 1Explore hintHide hint
Instead of asking each window for its minimum, ask each element: what is the longest window in which I am the minimum?
2Hint 2Explore hintHide hint
An element stays the minimum until a strictly smaller element appears on its left or right. Find the nearest smaller element on each side with a monotonic stack.
3Hint 3Explore hintHide hint
If arr[i]'s span (between nearest smaller neighbours) has length L, then arr[i] is a candidate answer for window size L — and for every size below L. Take per-size maxima, then propagate from large sizes down to small.
A · Core idea
Turn the problem inside out. Rather than computing each window's minimum, determine for each element the largest window in which it is the minimum.
The span of an element. arr[i] is the minimum of a window exactly while no smaller value is inside that window. So extend left until you hit the nearest element strictly smaller than arr[i] (call its index prevSmaller[i]), and right until the nearest strictly smaller element (nextSmaller[i]). The widest window where arr[i] reigns as minimum has length len = nextSmaller[i] − prevSmaller[i] − 1.
Computing prev/next smaller in O(n). A monotonic increasing stack does both. Scanning left to right, before pushing i we pop every index whose value is ≥ arr[i]; whatever remains on top is the previous strictly-smaller element (or −1 if the stack empties). A symmetric right-to-left scan gives nextSmaller (boundary n). Each index is pushed and popped once, so it's linear.
Mapping spans to answers. For each i, arr[i] is a valid candidate for window size len. Record ans[len] = max(ans[len], arr[i]). Multiple elements can share a length; we keep the largest.
The descending sweep — the subtle finish. An element that is the minimum of a length-L window is also the minimum of sub-windows of every smaller size within it, so it is a candidate for all sizes ≤ L. After filling ans[len] directly, sweep from w = n−1 down to 1 doing ans[w] = max(ans[w], ans[w+1]). This lets a strong answer found at a large length flow down to fill any smaller size that didn't get a bigger candidate of its own.
Handling ties. Popping on ≥ (not >) in both passes assigns each window of equal-valued runs to a single representative, preventing a value from being counted as the minimum of an overlapping span twice. The per-size max is unaffected because the true best still wins, but it keeps spans clean.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [6, 3, 5, 1, 12]Array state
element 3 @ i=1
prevSmaller=-1, nextSmaller=3 → span length 3
[12, 3, 3, 1, 1]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The pivot from 'minimum of each window' to 'span where each element is the minimum' is the entire trick — and it's the same monotonic-stack span idea behind Largest Rectangle in Histogram and Sum of Subarray Minimums. Once you see 'nearest smaller on both sides', a whole family of O(n²)-looking problems collapse to O(n).
ComplexityO(n) time, O(n) space — Each element is pushed and popped from the stack at most once across both passes → O(n). The final descending sweep is O(n). Arrays of size n → O(n) space.
E · Edge cases and pitfalls8arr = [5] — prevSmaller=-1, nextSmaller=1 → span 1 → ans[1]=5 → [5].Explore edge casesHide edge cases
Edge cases handled
arr = [5]prevSmaller=-1, nextSmaller=1 → span 1 → ans[1]=5 → [5].arr = [4, 4]With ≥-pops, spans give ans[1]=4 and the whole-array min sweep yields [4, 4].arr = [1, 2, 3, 4, 5]Increasing: element 5 has span 1, element 1 span 5 → [5, 4, 3, 2, 1].
Pitfalls
- Mixing up the span formula — it's nextSmaller − prevSmaller − 1, not nextSmaller − prevSmaller.
- Skipping the descending sweep — smaller window sizes are left at 0 or too small.
- Using > instead of ≥ inconsistently across the two passes, double-counting equal runs.
- Sweeping in ascending order — answers must flow from large sizes DOWN to small ones.
- Using int sums for values up to 10⁹ where products overflow (here we only compare, so long is safe; still prefer long).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.util.*; public long[] riddle(long[] arr) { int n = arr.length; int[] prevSmaller = new int[n];Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Skipping the descending sweep
Directly assigning ans[span] only fills the exact largest length each element wins. Smaller sizes that lack their own large candidate stay zero. A length-L answer is valid for all sizes ≤ L, so you must propagate downward.
Explore the code comparisonHide the code comparison
Skipping the descending sweep
Directly assigning ans[span] only fills the exact largest length each element wins. Smaller sizes that lack their own large candidate stay zero. A length-L answer is valid for all sizes ≤ L, so you must propagate downward.
Don't write this
for i in range(n):
length = next_smaller[i] - prev_smaller[i] - 1
ans[length] = max(ans[length], arr[i])
return ans[1:n + 1] # smaller sizes may still be 0Write this instead
for i in range(n):
length = next_smaller[i] - prev_smaller[i] - 1
ans[length] = max(ans[length], arr[i])
for w in range(n - 1, 0, -1):
ans[w] = max(ans[w], ans[w + 1])
return ans[1:n + 1]2Wrong span boundary formula
The span counts elements strictly between the two smaller neighbours. Using nextSmaller − prevSmaller overcounts by one and misassigns elements to a window size one too large.
Explore the code comparisonHide the code comparison
Wrong span boundary formula
The span counts elements strictly between the two smaller neighbours. Using nextSmaller − prevSmaller overcounts by one and misassigns elements to a window size one too large.
Don't write this
int len = nextSmaller[i] - prevSmaller[i]; // off by one
ans[len] = Math.max(ans[len], arr[i]);Write this instead
int len = nextSmaller[i] - prevSmaller[i] - 1;
ans[len] = Math.max(ans[len], arr[i]);Revise
Carry the pattern into the interview
Pattern to remember
Monotonic stack → span where each element is the minimum
Code pattern to memorize
For each i: span = nextSmaller[i] - prevSmaller[i] - 1
ans[span] = max(ans[span], arr[i])
for w from n-1 down to 1: ans[w] = max(ans[w], ans[w+1])Rules to remember
- 1Each element
arr[i]is the minimum of every window contained in its span(prevSmaller, nextSmaller); the largest such window has lengthnextSmaller − prevSmaller − 1. - 2An element that is the answer for window length L is also a valid candidate for every length smaller than L — hence the final descending sweep.
- 3Compute previous-smaller and next-smaller indices with a monotonic increasing stack, popping while the stacked value is
≥the current value. - 4Use a boundary of
-1on the left andnon the right for elements with no smaller neighbour. - 5Answer array is 1-indexed by window size;
ans[w]is the result for windows of sizew.
Use this pattern when
- 'For every window size, the max/min of window mins/maxes' style aggregate queries
- You need the span in which each element dominates as the min (or max)
- Histogram / largest-rectangle relatives where each bar's reach matters
- Any time brute force is O(n²) over windows and you want O(n) via spans
Choose another pattern when
- A single fixed window size → sliding-window deque (Sliding Window Maximum), not per-size spans
- You need the actual windows, not just the aggregate → spans don't reconstruct them directly
- Values can repeat heavily and you need exact counts → be careful with ≥ vs > to avoid double-count
Interview takeaway
Replace 'iterate every window' with 'for each element, what is the largest window where it's the minimum?'. A monotonic stack gives every element's span in O(n); a descending max-sweep then fills all smaller sizes. This span-of-dominance idea powers Largest Rectangle and Sum of Subarray Minimums too.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternMonotonic-stack span-of-dominance: for each element find the nearest strictly-smaller neighbours, derive the largest window it's the min of, then propagate answers from large window sizes down to small. Same machinery as Largest Rectangle and Sum of Subarray Minimums.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Largest Rectangle in HistogramComing soonSame nearest-smaller spans: each bar's width × height; take the max area.
- Sum of Subarray MinimumsComing soonCount subarrays where each element is the minimum via prev/next smaller spans, weighted sum.
- Sliding Window MaximumComing soonFixed window size instead of all sizes — monotonic deque, not per-size spans.