Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
What is Stack?
Stack is a LIFO (Last In, First Out) data structure where elements are added and removed from the same end — the top. Think of it like a spring-loaded plate dispenser in a cafeteria: you can only take the top plate, and when a new plate is added, it goes on top. You never reach into the middle. That constraint — only the most recent item is accessible — is exactly what makes stacks powerful for problems that care about recency and nesting. In algorithm problems, stacks appear in three major families: matching problems (parentheses, tags, nested structures), expression evaluation (calculators, compilers), and monotonic stack problems (next greater/smaller element, histograms). The key insight is always the same: when the “most recently seen” item determines what happens next, a stack is the right tool. Key operations and their complexity:push(add to top): O(1)pop(remove from top): O(1)peek/top(view top without removing): O(1)
list works perfectly as a stack (append and pop are both O(1)). But in Java, prefer ArrayDeque over Stack — the Stack class is synchronized and inherits from Vector, adding unnecessary overhead. The Java docs themselves recommend Deque as a replacement.
Pattern Recognition Cheatsheet
Reusable Template Code
Three skeletons cover the vast majority of stack problems. Internalize them and you will write each one in under a minute under interview pressure.Pattern Recognition Checklist
Use Stack When
- Matching parentheses/brackets/tags
- Evaluating expressions (postfix/infix)
- Finding next/previous greater/smaller
- Processing nested structures
- Implementing undo/back operations
Don't Use When
- Need random access to elements
- FIFO ordering needed (use Queue)
- Need to process from both ends
- Simple array traversal suffices
Stack Type Quick Reference
| Problem Type | Stack Type | Direction |
|---|---|---|
| Next Greater Element | Monotonic Decreasing | Left to Right |
| Previous Greater Element | Monotonic Decreasing | Left to Right |
| Next Smaller Element | Monotonic Increasing | Left to Right |
| Largest Rectangle | Monotonic Increasing | Left to Right |
| Trapping Rain Water | Either | Two-pointer or stack |
When to Use
Matching Problems
Expression Evaluation
Monotonic Stack
Undo Operations
Pattern Variations
1. Valid Parentheses
The classic stack problem. The core insight: the most recently opened bracket must be the first one closed. That is exactly LIFO ordering. A counter alone cannot solve this for multiple bracket types —([)] has balanced counts but invalid nesting. Only a stack tracks the order of opens.
Complexity: O(n) time, O(n) space in the worst case (all opening brackets).
Edge cases: Empty string (valid), single character (always invalid), only opening brackets (stack leftover — invalid), only closing brackets (empty stack check catches it).
2. Decode String
This problem extends the matching pattern by storing context (the accumulated string and its repeat count) on the stack. Think of it like nested function calls: each[ opens a new scope with its own local variables. On ], you return the result to the caller scope.
Complexity: O(output_length) time — the output can be exponentially larger than the input (e.g., 10[10[10[a]]] produces 1000 characters from ~15 input characters).
Edge case: Multi-digit numbers like 12[a] — you must build the number digit by digit with current_num = current_num * 10 + int(char).
3. Basic Calculator
The stack acts as a context saver here. On(, you pause the outer evaluation by pushing the running result and current sign, then start fresh inside the parentheses. On ), you merge the inner result back into the outer context. This is exactly how call stacks work in programming languages — every function call saves the caller’s state, and every return restores it.
Complexity: O(n) time, O(d) space where d is the maximum nesting depth.
Edge cases: Leading minus sign (e.g., "-1+2"), spaces between characters, deeply nested expressions like ((((1+2)))).
4. Min Stack
The key insight: a single stack cannot give O(1) minimum because popping might change the min, and you would need to scan. The fix is a second “min stack” that mirrors the main stack’s minimum at every height. The space-time trade-off is the core of this problem. Critical detail: Push to the min stack whenval <= min_stack[-1], NOT when val < min_stack[-1]. The <= handles duplicate minimums. Without it, popping one copy of the minimum incorrectly removes it from the min stack while another copy still exists on the main stack.
Complexity: O(1) for all operations. O(n) extra space in the worst case (strictly decreasing input fills both stacks equally).
5. Daily Temperatures (Monotonic Stack)
A direct application of the “next greater element” pattern. The stack holds indices of days still “waiting” for a warmer day. When a warmer day arrives, it resolves all waiting days that are cooler — each popped element records the distance (index difference) to the warmer day. Think of the stack as a waiting room: days sit there until someone warmer shows up, then they leave and never come back. Complexity: O(n) time (each element is pushed and popped at most once — amortized O(1) per element). O(n) space for the stack. Interview tip: Always store indices on the stack, not values. You need the index for the distance calculation (i - idx), and you can always look up the value via temperatures[idx].
Classic Problems
1. Valid Parentheses
1. Valid Parentheses
2. Largest Rectangle in Histogram
2. Largest Rectangle in Histogram
3. Evaluate Reverse Polish Notation
3. Evaluate Reverse Polish Notation
4. Simplify Path
4. Simplify Path
Monotonic Stack Deep Dive
The monotonic stack is one of the most powerful — and most misunderstood — patterns. The core principle: maintain elements on the stack in sorted order, and extract answers at the moment you pop. Every element is pushed exactly once and popped at most once, giving O(n) amortized time even though the code has a while-loop inside a for-loop. Decision guide: Use a monotonic decreasing stack when searching for the “next greater” element. Use a monotonic increasing stack when searching for the “next smaller” element or computing areas in histograms. The rule: the stack direction is opposite to what you are searching for.Worked LeetCode Problems: Brute Force vs Optimized
Five canonical problems cover the entire stack interview surface. For each, name the brute force first, then pivot to the stack solution. Interviewers love hearing the trade-off out loud.LC 20: Valid Parentheses (Easy)
Problem. Given a string containing only()[]{}, determine if it is valid — every opener has a matching closer in the right order.
Brute force (O(n^2)). Repeatedly scan and remove inner matched pairs ((), [], {}) until nothing changes. Slow and ugly.
Optimized stack (O(n) time, O(n) space). Push openers; on a closer, the stack top must be the matching opener. Empty stack at the end means valid.
([)]. Only a stack tracks the order of opens.
LC 155: Min Stack (Medium)
Problem. Design a stack that supportspush, pop, top, and getMin, all in O(1).
Brute force. Scan the whole stack on getMin — O(n) per call. Fails the constraint.
Optimized — auxiliary min stack. Maintain a parallel stack whose top is always the current minimum. Push to the aux stack only when the new value is <= the current min (the <= is critical for handling duplicate minimums).
<= trap. With strict <, pushing [2, 0, 0] and popping the first 0 removes it from the min stack. Now getMin() returns 2 even though a 0 is still on the main stack. The interviewer is testing whether you spot this.
LC 224: Basic Calculator (Hard)
Problem. Evaluate an expression containing+, -, (, ) and integers.
Brute force. Convert infix to RPN with shunting-yard, then evaluate. Works but is overkill for +/-/().
Optimized — one stack tracking running result and sign. On (, snapshot the current result and sign onto the stack and reset; on ), pop them and merge the inner result into the outer context.
1 - (2 + 3) returns the wrong answer because the outer minus is lost. Push BOTH.
LC 71: Simplify Path (Medium)
Problem. Given a Unix-style absolute path, return the canonical form. Brute force. String manipulation with regex — error-prone and brittle for edge cases like/// or /...
Optimized stack. Split by /. For each component: ignore . and empty strings; pop on ..; push otherwise. Join with /.
/../ should return /. /home//foo/ should return /home/foo. /... should return /... (a literal directory named ...).
LC 739: Daily Temperatures (Medium) — Bridge to Monotonic Stack
Problem. For each day, return how many days until a warmer temperature. Brute force (O(n^2)). For each day, scan forward until warmer. Optimized monotonic stack (O(n)). Stack holds indices of days waiting for a warmer day. When today is warmer than the day on top, pop and recordtoday - popped_index.
Common Mistakes
Curated LeetCode Practice List
The classic stack problem set, grouped by difficulty. Solve them in order within each tier.Easy
| # | Problem | Pattern Variant |
|---|---|---|
| 20 | Valid Parentheses | Bracket matching |
| 232 | Implement Queue using Stacks | Two-stack amortization |
| 225 | Implement Stack using Queues | Reverse trick |
| 1047 | Remove All Adjacent Duplicates | Push-or-cancel |
| 844 | Backspace String Compare | Stack simulation |
| 682 | Baseball Game | Direct stack ops |
Medium
| # | Problem | Pattern Variant |
|---|---|---|
| 155 | Min Stack | Auxiliary stack for O(1) min |
| 150 | Evaluate RPN | Operand stack |
| 71 | Simplify Path | Path component stack |
| 394 | Decode String | Save-restore context |
| 739 | Daily Temperatures | Monotonic decreasing |
| 503 | Next Greater Element II | Circular monotonic |
| 735 | Asteroid Collision | Conditional pop on collision |
| 901 | Online Stock Span | Streaming monotonic |
Hard
| # | Problem | Pattern Variant |
|---|---|---|
| 84 | Largest Rectangle in Histogram | Monotonic increasing |
| 85 | Maximal Rectangle | Per-row histogram |
| 224 | Basic Calculator | Save-restore context |
| 772 | Basic Calculator III | Two stacks plus precedence |
| 42 | Trapping Rain Water | Monotonic decreasing |
Common Mistakes
Debugging Checklist
Interview Problems by Company
- Easy
- Medium
- Hard
| Problem | Company | Key Concept |
|---|---|---|
| Valid Parentheses | All FAANG | Push open, pop close |
| Baseball Game | Amazon | Stack operations |
| Remove Outer Parens | Counter or stack | |
| Backspace String | Stack simulation |
Interview Tips
How to Explain Your Approach
How to Explain Your Approach
- “This is a matching problem, so I’ll use a stack.”
- “Opening brackets push, closing brackets pop and compare.”
- “If match fails or stack has leftovers, it’s invalid.”
- “For monotonic: I maintain a stack where elements are always sorted.”
When Interviewer Says...
When Interviewer Says...
| Interviewer Says | You Should Think |
|---|---|
| ”Valid parentheses/brackets” | Basic stack push/pop |
| ”Next greater element” | Monotonic decreasing stack |
| ”Largest rectangle” | Monotonic increasing stack |
| ”Evaluate expression” | Two stacks (operand + operator) |
| “Simplify path” | Stack for directory navigation |
Monotonic Stack Template
Monotonic Stack Template
Practice Problems
| Problem | Difficulty | Link |
|---|---|---|
| Valid Parentheses | Easy | LeetCode 20 |
| Min Stack | Medium | LeetCode 155 |
| Daily Temperatures | Medium | LeetCode 739 |
| Largest Rectangle in Histogram | Hard | LeetCode 84 |
| Basic Calculator | Hard | LeetCode 224 |
Practice Roadmap
Day 3: Monotonic Stack
- Solve: Next Greater Element, Daily Temperatures
- Focus: When to pop from stack
Interview Questions
Deep-dive questions that test real understanding of the Stack pattern, not just whether you can push and pop. Expect interviewers to keep digging past your first answer.Q1: Walk me through how you would validate a string containing parentheses, brackets, and braces. What makes a stack the right choice here?
Q1: Walk me through how you would validate a string containing parentheses, brackets, and braces. What makes a stack the right choice here?
- The key insight is that the most recently opened bracket must be the first one closed. That is exactly the LIFO property. If I see
({[, the[must close before the{, which must close before the(. A stack naturally enforces this ordering. - Algorithm: Iterate through the string. Push every opening bracket. On a closing bracket, check that the stack is not empty AND the top matches. Pop on match, return false on mismatch. At the end, the stack must be empty — leftover openers mean unclosed brackets.
- Why not a counter? A single counter works for one bracket type (just track
open - close >= 0), but with multiple types it fails.([)]has balanced counts but is invalid because the nesting order is wrong. Only a stack tracks the order of opens. - Real-world connection: This exact algorithm is the first pass of every compiler’s lexer and every JSON/XML parser. VS Code’s bracket matching uses this logic on every keystroke.
([)] is invalid despite balanced counts. Another red flag: not checking for empty stack before peeking — this is the #1 runtime error in stack problems.Follow-ups:- What if the string contains non-bracket characters (like
a * (b + c) - {d}}? How does your solution change? (Answer: simply skip characters not in the mapping or the opener set. The algorithm is identical otherwise.) - What if I ask you to return the index of the first mismatched bracket instead of just true/false? How does your approach adapt? (Answer: track indices alongside characters on the stack. On mismatch, you have the index immediately. On leftover openers, the bottom of the stack gives the earliest unclosed index.)
Q2: Design a stack that supports push, pop, top, and retrieving the minimum element, all in O(1) time. What is the core design trade-off?
Q2: Design a stack that supports push, pop, top, and retrieving the minimum element, all in O(1) time. What is the core design trade-off?
- The core insight: a single stack cannot give O(1) minimum because removing an element might change the minimum, and you would need to scan. The fix is a second stack (the min stack) that mirrors the main stack’s minimum at every height.
- Push: Push to main stack. Push to min stack if the value is
<=the current min (or if min stack is empty). The<=is critical — not<. If I push[2, 0, 0]and use<, the min stack only has[2, 0]. When I pop the first0, the min stack pops its0, and nowgetMin()returns2even though a0is still on the main stack. - Pop: Pop from main stack. If the popped value equals the min stack’s top, pop the min stack too.
- Alternative approach: Instead of a separate min stack, store
(value, current_min)tuples on a single stack. Same O(n) space, slightly simpler logic, but doubles the memory per element. - Space analysis: Worst case (strictly decreasing input), the min stack is the same size as the main stack — O(n). Best case (strictly increasing input), the min stack has only one element. Average case depends on the distribution.
< instead of <= for the min stack push condition and not catching the duplicate minimum bug.Follow-ups:- Can you implement MinStack using O(1) extra space (not O(n))? Hint: store something other than the raw value. (Answer: store the difference between the value and the current min. When the stored value is negative, it means we pushed a new minimum. On pop, use the difference to recover the previous min. This is clever but fragile with integer overflow.)
- What if I also need O(1)
getMax()? Does one extra stack still suffice? (Answer: you need a third stack — a max stack. The pattern extends cleanly. Each auxiliary stack tracks one aggregate.)
Q3: Explain what a monotonic stack is, why it gives O(n) for 'next greater element,' and why a naive approach is O(n^2).
Q3: Explain what a monotonic stack is, why it gives O(n) for 'next greater element,' and why a naive approach is O(n^2).
- Naive approach: For each element, scan right until you find something greater. Worst case (sorted descending like
[5, 4, 3, 2, 1]), every element scans to the end. That is O(n^2). - Monotonic stack approach: Maintain a stack where elements are in decreasing order (from bottom to top). For each new element, pop everything smaller — those popped elements just found their “next greater.” Then push the current element.
- Why O(n): Each element is pushed exactly once and popped at most once. Total operations across the entire traversal: at most 2n. The while loop inside the for loop looks like it could be O(n) per iteration, but amortized across all iterations, the total pops cannot exceed n because we never pop something that was not previously pushed.
- The way I think about it: The stack is like a waiting room. Elements sit in the waiting room until someone greater shows up. Once they are “resolved” (popped), they never come back. Since each element enters and exits the waiting room exactly once, total work is O(n).
- Key detail: Store indices, not values. You almost always need the index for distance calculations (like in Daily Temperatures) or to write results back to an array.
- When would you use a monotonic increasing stack versus a monotonic decreasing stack? Give a concrete problem for each. (Answer: decreasing stack for “next greater element” — you pop when you see something bigger. Increasing stack for “next smaller element” or “largest rectangle in histogram” — you pop when you see something smaller.)
- How would you adapt this for a circular array where the “next greater” can wrap around? (Answer: iterate through the array twice — conceptually
2nelements — usingi % nfor indexing. Only push during the first pass. The second pass resolves elements whose next greater wraps around.)
Q4: In the Largest Rectangle in Histogram problem, why do we use a monotonic increasing stack? Walk me through what happens when we pop.
Q4: In the Largest Rectangle in Histogram problem, why do we use a monotonic increasing stack? Walk me through what happens when we pop.
width = i - stack[-1] - 1 (not i - popped_index). Getting the width wrong is the most common bug, and understanding it requires grasping what the stack invariant guarantees about the elements between indices.Strong answer:- Why monotonic increasing: We want to find, for each bar, the largest rectangle using that bar as the shortest bar. A bar can extend left and right as long as all bars in that range are at least as tall. A monotonic increasing stack guarantees that when we pop a bar, everything between the new stack top and the current index is at least as tall as the popped bar.
- The pop moment: When
heights[i] < heights[stack[-1]], the bar atstack[-1]can no longer extend right (it hit something shorter). So we pop it and calculate its rectangle. - Width calculation — the tricky part: After popping index
j, the width is NOTi - j. It isi - stack[-1] - 1(oriif the stack is empty). Why? Because everything between the new stack top andiwas already popped earlier, meaning those bars were all taller thanheights[j]. So the rectangle of heightheights[j]extends all the way fromstack[-1] + 1toi - 1. - Sentinel trick: Append a
0to the end of the heights array. This forces all remaining elements to be popped, avoiding a separate cleanup loop after the main iteration. It is a clean simplification that interviewers appreciate. - Example: For
[2, 1, 5, 6, 2, 3], when we hit the second2(index 4), we pop6(index 3): height = 6, width =4 - 2 - 1 = 1, area = 6. Then pop5(index 2): height = 5, width =4 - 1 - 1 = 2, area = 10. Thatwidth = 2is the key — it includes index 3 because the6at index 3 was taller than5.
width = i - popped_index instead of i - stack[-1] - 1. This is the single most common bug — it gives wrong answers when multiple bars have been popped. If a candidate cannot explain why previously-popped bars contribute to the width, they do not understand the invariant. Another red flag: not mentioning the sentinel trick and instead having a messy cleanup loop that handles edge cases differently.Follow-ups:- How would you extend this to the Maximal Rectangle problem (LC 85), where you find the largest rectangle of 1s in a binary matrix? (Answer: treat each row as a histogram where the bar height is the number of consecutive 1s above. Run the largest rectangle algorithm on each row’s histogram. O(rows * cols) total.)
- What is the relationship between this problem and Trapping Rain Water? Can you use the same stack technique? (Answer: Trapping Rain Water uses a monotonic decreasing stack — you pop when you see something taller because it means a valley has been found. The height calculation uses the min of the two boundaries minus the popped bar’s height, and width is computed the same way. Same technique, opposite monotonicity.)
Q5: How would you evaluate the expression '3[a2[bc]]' using a stack? What makes this harder than basic parentheses matching?
Q5: How would you evaluate the expression '3[a2[bc]]' using a stack? What makes this harder than basic parentheses matching?
- Why it is harder: In valid parentheses, the stack stores single characters. Here, we need to store context — the string we were building and the multiplier we saw before the
[. When we hit], we need to restore both. - The pattern: Maintain
current_stringandcurrent_numas running accumulators. On[, push(current_string, current_num)onto the stack and reset both. On], pop the saved state and computeprev_string + current_string * num. On digits, build the number. On letters, append tocurrent_string. - Why two accumulators, not just one stack? You could use a single stack and push everything (digits, brackets, characters), but then the
]handler has to scan backwards through the stack to find the matching[and extract the number. Using separate accumulators makes the]handler O(1) instead of O(k) where k is the segment length. - Nested example walkthrough for
3[a2[bc]]:- Read
3->current_num = 3 - Read
[-> push("", 3), reset tocurrent_str = "", current_num = 0 - Read
a->current_str = "a" - Read
2->current_num = 2 - Read
[-> push("a", 2), reset tocurrent_str = "", current_num = 0 - Read
b,c->current_str = "bc" - Read
]-> pop("a", 2),current_str = "a" + "bc" * 2 = "abcbc" - Read
]-> pop("", 3),current_str = "" + "abcbc" * 3 = "abcbcabcbcabcbc"
- Read
12[a] — you need current_num = current_num * 10 + int(char), not just int(char)).Follow-ups:- What if the encoding can have nested brackets without a number prefix, like
[abc]meaning repeat once? How does your code handle this edge case? (Answer: initializecurrent_numto default 1 when pushing, or treat missing numbers as 1. The code needs a minor tweak — check ifcurrent_num == 0on[and default it.) - What is the time complexity? It is not O(n) where n is the input length — explain why. (Answer: the output string can be exponentially larger than the input. For
10[10[10[a]]], the input is ~15 characters but the output is 1000 characters. Time is O(output_length), which can be much larger than input length. This matters for memory allocation decisions in production.)
Q6: In the Basic Calculator problem, how does the stack handle nested parentheses with mixed addition and subtraction? Why push both the result and the sign?
Q6: In the Basic Calculator problem, how does the stack handle nested parentheses with mixed addition and subtraction? Why push both the result and the sign?
- The challenge: Parentheses create nested evaluation contexts. The expression
1 - (2 + 3)requires evaluating2 + 3 = 5in its own context, then applying the-sign from the outer context. You need to “pause” the outer evaluation, compute the inner one, and “resume.” - Why push both result and sign: On
(, push the running result so far and the sign before the parenthesis. Reset result to 0 and sign to 1. This creates a clean inner context. On), finish the inner computation, then multiply by the saved sign and add to the saved result. This “merges” the inner result back into the outer context. - Walkthrough of
1 - (2 + 3):- Read
1-> result = 1 - Read
--> sign = -1 - Read
(-> push(1, -1), reset result = 0, sign = 1 - Read
2-> result = 2 - Read
+-> sign = 1 - Read
3-> result = 5 - Read
)-> pop(1, -1), result =5 * (-1) + 1 = -4
- Read
- Why not Shunting-yard or RPN conversion? Those work but are overkill for
+,-,(,). The direct evaluation with a stack is simpler and avoids building an intermediate representation. If the problem adds*and/with precedence, then Shunting-yard becomes worth the added complexity.
1 - (2 - 3).Follow-ups:- How would you extend this to support
*and/with correct operator precedence? (Answer: you now need two stacks — one for operands and one for operators — or you need to process*and/immediately while deferring+and-until later. Alternatively, convert to RPN with Shunting-yard then evaluate.) - What happens with deeply nested expressions like
((((1 + 2))))— is there a stack depth concern? (Answer: stack depth equals nesting depth. For user-generated input, you should add a max nesting depth check to prevent stack overflow in production. Compilers typically limit nesting depth for this reason.)
Q7: What are the most common bugs in stack-based solutions, and how do you systematically prevent them?
Q7: What are the most common bugs in stack-based solutions, and how do you systematically prevent them?
- Bug #1: Empty stack access. Calling
peek()orpop()on an empty stack. This is the single most common runtime crash. Prevention: always guard withif stack(Python),!stack.isEmpty()(Java), or!stk.empty()(C++) before every access. Make it a reflex, not a conscious decision. - Bug #2: Forgetting remaining elements. After the main loop, the stack may still contain unprocessed elements. In “next greater element,” remaining elements have no greater element to their right and should be set to -1. In “largest rectangle,” remaining bars still need their areas calculated. Prevention: either process the stack after the loop or use a sentinel value (append 0 to the array) that forces everything to pop.
- Bug #3: Storing values instead of indices. If you push values instead of indices, you lose positional information needed for distance calculations.
Daily Temperaturesreturns how many days until warmer, not the temperature itself. Prevention: default to storing indices; you can always look up the value viaarr[index]. - Bug #4: Wrong monotonic direction. Using an increasing stack when you need decreasing (or vice versa). This flips the problem from “next greater” to “next smaller.” Prevention: before coding, explicitly write a comment like
# monotonic decreasing: pop when current > topand verify against a small example. - Bug #5: Off-by-one in width calculations. In largest rectangle, the width is
i - stack[-1] - 1, noti - popped_index. This is because previously-popped elements between the new top andiwere taller. Prevention: trace through a small example (e.g.,[2, 1, 5, 6, 2, 3]) on paper before coding.
- In Java,
Stack<Integer>usesIntegerobjects. What subtle bug can occur when comparing stack top with a value using==instead of.equals()? (Answer:==compares object references, not values. For integers outside the cached range of -128 to 127, twoIntegerobjects with the same value can have different references.stack.peek() == valuemay return false even when the values match. Always use.equals()or unbox explicitly.) - How do you test a stack-based solution efficiently? What edge cases do you always check? (Answer: empty input, single element, all elements the same, strictly increasing input, strictly decreasing input, and the input that triggers the worst-case stack depth. These cover all boundary conditions for stack behavior.)
Q8: Explain how you would use a stack to solve the Trapping Rain Water problem. When would you prefer this over the two-pointer approach?
Q8: Explain how you would use a stack to solve the Trapping Rain Water problem. When would you prefer this over the two-pointer approach?
- How the stack approach works: Maintain a monotonic decreasing stack of indices. When
height[i] > height[stack[-1]], a valley has been found. Pop the valley bottom, then calculate the water trapped between the new stack top (left boundary) and the current index (right boundary). - Water calculation on pop:
height = min(height[stack[-1]], height[i]) - height[popped](bounded height minus valley floor).width = i - stack[-1] - 1. Water for this layer =height * width. This adds water in horizontal layers, not vertical columns. - Key difference from two-pointer: Two pointers calculate water column by column (vertical slices): for each position,
water += min(left_max, right_max) - height[i]. The stack calculates water layer by layer (horizontal slices): for each valley, compute the rectangular strip of water trapped. - When to prefer the stack:
- When the input is a stream and you do not have the full array upfront. The stack processes left-to-right and does not need a right-to-left pass.
- When the problem variation asks “how many distinct pools of water are formed” — the stack naturally segments water into contiguous pools as each valley is processed.
- When to prefer two pointers: When you need O(1) space (the stack is O(n) worst case) and you have the full array.
- What is the worst-case space complexity of the stack approach? Give a concrete input that triggers it. (Answer: O(n) when the input is strictly decreasing like
[5, 4, 3, 2, 1]. Nothing gets popped until you hit a taller bar, so all indices accumulate on the stack.) - If the input heights can be floating-point numbers, does the stack approach still work correctly? Are there precision concerns? (Answer: the algorithm works identically. However, floating-point addition is not associative, so the order in which horizontal layers are summed can cause tiny precision differences compared to the two-pointer approach. For financial or engineering calculations, you might need to use
decimaltypes or track an error bound.)
Q9: How would you implement a browser's back/forward navigation using stacks? What happens if the user navigates to a new page while the forward stack is non-empty?
Q9: How would you implement a browser's back/forward navigation using stacks? What happens if the user navigates to a new page while the forward stack is non-empty?
Q10: Given the next greater element problem on a circular array, how do you adapt the monotonic stack? Why does iterating twice through the array work?
Q10: Given the next greater element problem on a circular array, how do you adapt the monotonic stack? Why does iterating twice through the array work?
- The problem with circularity: In a linear array, elements at the end have no “next greater” to the right. In a circular array, the search wraps around — the next greater element for the last element could be the first element.
- The trick: Iterate
2ntimes usingi % nfor indexing. The first pass (i = 0ton-1) works exactly like the linear version. The second pass (i = nto2n-1) revisits elements, allowing wrap-around resolution. Only push indices during the first pass (i < n), or pushi % nthroughout — but only write results for indices less thann. - Why one extra pass suffices: After the first pass, the stack contains indices whose next greater element was not found in the linear scan. During the second pass, each of these elements gets compared against elements from the start of the array. Since the second pass covers the entire array, every element gets a chance to be the “next greater” for every unresolved element. No element needs more than one full wrap-around to find its answer (or confirm none exists).
- What about elements that truly have no next greater? In a circular array, an element has no next greater only if it is the maximum of the entire array. After
2niterations, such elements remain on the stack. Their result stays as the initialized-1.
i % n achieves the same effect with O(1) extra space. Another red flag: iterating 3n or more times “just to be safe” — this shows the candidate does not understand why 2n is exactly sufficient.Follow-ups:- What is the time and space complexity? Is the
2niteration still O(n)? (Answer: yes, O(n) time — each of the n elements is pushed once and popped at most once. The second pass only causes pops, no new pushes. Space is O(n) for the stack and result array.) - What if instead of “next greater,” I ask for the “next greater element that is at least K positions away” in a circular array? Does the monotonic stack still work directly? (Answer: not directly. The monotonic stack resolves the nearest next greater. For a minimum-distance constraint, you would need to delay processing — only pop an element when the current index is at least K positions ahead of it. This requires careful index arithmetic with the modular wrapping.)
Interview Q&A Rapid-Fire (Rich Format)
The four problems below mirror the format you will see in our other DSA chapters: strong answer framework, real LeetCode example with full code, three senior follow-ups, common wrong answers, and further reading.Q1: Implement Min Stack with O(1) getMin
Q1: Implement Min Stack with O(1) getMin
- Identify the constraint. “All four operations — push, pop, top, getMin — must be O(1). A naive single stack makes getMin O(n).”
- Introduce the auxiliary structure. “I will keep a parallel min-stack tracking the running minimum at every height of the main stack.”
- Push rule. Push to main always. Push to min-stack only if the new value is
<=the current min (the<=is critical for duplicates). - Pop rule. Pop main always. If the popped value equals the min-stack top, pop min-stack too.
- Justify
<=vs<. Trace[2, 0, 0]: with<, popping the first0removes it from the min-stack andgetMinreturns2while a0is still on main. With<=, the second0is also pushed onto min-stack, preserving correctness.
val - current_min onto a single stack. When the stored value is negative, it signals a new min (and you can recover the previous min by subtraction). Clever but fragile — integer overflow with extreme values is a real concern.val >= max_stack[-1]. Each auxiliary stack tracks one aggregate. The pattern composes cleanly.- “Track the minimum in a single variable” — breaks on pop. After popping the current min, you do not know the new min without scanning.
- “Use
<instead of<=for the min-stack push condition” — silently wrong on duplicate minimums. - “Scan the stack on getMin” — O(n) per call, fails the constraint.
Q2: Evaluate Reverse Polish Notation
Q2: Evaluate Reverse Polish Notation
- Recognize the postfix advantage. “RPN is unambiguous — no parentheses, no precedence rules. The stack evaluates it in one linear pass.”
- State the algorithm. Iterate tokens. If number, push. If operator, pop two operands, apply, push result.
- Order matters for non-commutative operators. For
a - b, the second pop isa(the left operand) and the first pop isb(the right operand). Same for division. - Handle integer division semantics. Some problems require truncation toward zero (LC 150 specifically), not Python’s floor division.
- Verify the final state. After processing all tokens, exactly one value remains on the stack: the result.
int(a / b) instead of a // b in Python?” — Python’s // is floor division, which rounds toward negative infinity. The problem requires truncation toward zero. int(a / b) truncates correctly: int(-7 / 2) == -3 (truncated), while -7 // 2 == -4 (floored). This is a common subtle bug.max(a, b) or unary operators like -x?” — Extend the parser: function names go on the operator stack with their argument count. Unary minus is detected by context (no preceding operand). The shunting-yard algorithm handles all this; LeetCode rarely tests it but production calculators must.- “Pop in the wrong order” —
a - bbecomesb - a. The interviewer is watching for whether you say “left operand popped second” out loud. - “Use
//for division” — wrong for negative numbers (LC 150 specifically tests this). - “Build a parse tree first” — correct but overkill; the stack handles RPN in one pass with no tree.
Q3: Valid Parentheses with three bracket types
Q3: Valid Parentheses with three bracket types
- Articulate the LIFO insight. “The most recently opened bracket must be the first one closed — exactly the LIFO property of a stack.”
- Reject the counter approach. A single counter passes
([)](balanced counts but invalid nesting). Only a stack tracks order. - Set up a closer-to-opener map for O(1) match lookup.
- One pass through the string. Push openers; on closers, the stack top must be the matching opener.
- End-state check. Empty stack means valid — leftover openers mean unclosed brackets.
addChar(c) and isValid() queries.” — Same stack, but expose it as state. addChar does the push or pop; isValid checks if the stack is empty AND no mismatch has occurred. Keep a is_dirty flag for early failures.- “Use one counter per bracket type” — passes
([)], which is wrong. - “Use regex to repeatedly remove
(),[],{}” — works for matched-only but is O(n^2) in the worst case. - “Forget the empty-stack check” — crashes on inputs that start with a closer like
).
Q4: Daily Temperatures -- bridge from stack to monotonic stack
Q4: Daily Temperatures -- bridge from stack to monotonic stack
- Recognize the pattern. “This is ‘next greater element’ but instead of recording the value, I record the day distance.”
- Brute force first. O(n^2) — for each day, scan forward. Too slow.
- Monotonic stack of indices. Days waiting for warmer weather sit on the stack; today’s temperature pops every day on top that is colder.
- Why indices, not values. We need
i - jto compute the wait distance. - Default to 0. Days remaining on the stack at the end never found a warmer day — the array’s default zero already handles them.
- Prove O(n) amortized. Each day is pushed once and popped at most once.
temps[i] < temps[stack[-1]]. The skeleton is identical; only the comparison flips.(value, span_count) pairs. On a new value, accumulate spans of all popped smaller values into the current span. Amortized O(1) per call.- “Brute force scanning forward from each day” — O(n^2), times out.
- “Push values, not indices” — cannot compute the distance
i - j. - “Forget that remaining stack elements should map to 0” — relying on default initialization is fine but candidates often add a redundant cleanup loop and break it.