A monotonic stack is a powerful data structure and algorithm pattern that maintains elements in either strictly increasing or decreasing order. It’s particularly useful for solving array-based problems that involve finding the next greater or smaller element, calculating spans, or identifying boundaries efficiently. Unlike brute-force approaches that might require O(n²) time complexity, monotonic stacks often solve these problems in just O(n) time, making them perfect for technical interviews.

In this post, I’ll guide you through six classic LeetCode problems that leverage the monotonic stack pattern, from straightforward applications to more complex scenarios.

1. Daily Temperatures (LeetCode 739, Medium)

Problem:

Given an array of integers temperatures representing daily temperatures, return an array answer such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature. If there is no future day for which this is possible, keep answer[i] = 0.

Example:

Python Solution:

class Solution:
def dailyTemperatures(self, nums: List[int]) -> List[int]:
stack = []
ans = [0] * len(nums)
for cur_day, cur_temp in enumerate(nums):
while stack and cur_temp > stack[-1][1]:
past_day, past_temp = stack.pop()
ans[past_day] = cur_day - past_day
stack.append((cur_day, cur_temp))
return ans

Explanation:

This is a classic application of a monotonic decreasing stack. We store pairs of (day_index, temperature) in our stack. For each current temperature:

  1. While the stack is not empty and the current temperature is warmer than the temperature at the top of the stack, we pop the stack.
  2. For each popped entry, we calculate the waiting days by subtracting the past day from the current day.
  3. We push the current day and temperature onto the stack.

Time Complexity: O(n) — Each element is pushed and popped at most once.
Space Complexity: O(n) — In the worst case, the stack stores all temperatures.

Edge Cases:

2. Next Greater Element II (LeetCode 503, Medium)

Problem:

Given a circular integer array nums (i.e., the next element of nums[nums.length - 1] is nums), return the next greater number for every element in nums. The next greater number of a number x is the first greater number to its traversing-order next in the array, which means you could search circularly to find its next greater number. If it doesn't exist, return -1 for this number.

Example:

Python Solution:

class Solution:
def nextGreaterElements(self, nums):
n = len(nums)
nums = nums + nums # Duplicate array to handle circular nature
out = [-1] * n
stack = []
for index, val in enumerate(nums):
while stack and stack[-1][1] < val:
out[(stack[-1][0]) % n] = val
stack.pop()
stack.append((index, val))
return out

Explanation:

This problem extends the classic “next greater element” problem by adding the circular array constraint. The elegant approach here:

  1. Duplicate the array to simulate the circular nature (nums + nums)
  2. Use a monotonic decreasing stack to track indices and values
  3. When a greater element is found, update the result array while accounting for the circular nature using modulo operation
  4. Continue until all elements are processed

Time Complexity: O(n) — Each element is processed at most twice.
Space Complexity: O(n) — Stack size and duplicated array.

Edge Cases:

3. Largest Rectangle in Histogram (LeetCode 84, Hard)

Problem:

Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.

Example:

Python Solution:

class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
"""
Algorithm:
- push -1 on the stack
- keep pushing on the stack if the cur height is greater than one on the top of the stack
- keep only indexes in the stack
- while popping any height
- the max rectangle with that height is
- first height < cur on the left and on the right.
- i.e. cur - stack top - 1 * cur_height
- add additional zero towards the end to make popping easy
"""
heights.append(0) # Add sentinel to ensure all bars are processed
stack = [-1] # Initialize with -1 to handle edge cases
ans = 0

for index in range(len(heights)):
while stack[-1] != -1 and heights[stack[-1]] > heights[index]:
ans_index = stack.pop()
left, right = stack[-1], index
ans = max(ans, (right - left - 1) * heights[ans_index])
stack.append(index)
return ans

Explanation:

This is a more complex application of monotonic stacks. The key insight:

  1. For each bar, the maximum rectangle with that bar as the height extends to the nearest shorter bar on both sides
  2. We use a monotonic increasing stack to track indices
  3. When we find a shorter bar, we pop the stack and calculate rectangles
  4. The sentinel value 0 at the end ensures all bars are processed
  5. The sentinel -1 at the beginning helps calculate widths correctly

Time Complexity: O(n) — Each bar is pushed and popped exactly once.
Space Complexity: O(n) — Stack size in worst case.

Edge Cases:

4. Number of Visible People in a Queue (LeetCode 1944, Hard)

Problem:

There are n people standing in a queue, numbered from 0 to n-1 in left to right order. You are given an array heights of distinct integers where heights[i] represents the height of the ith person. A person can see another person to their right in the queue if everybody in between is shorter than both of them. More formally, the ith person can see the jth person if i < j and min(heights[i], heights[j]) > max(heights[i+1], heights[i+2], ..., heights[j-1]). Return an array answer where answer[i] is the number of people the ith person can see to their right in the queue.

Example:

Python Solution:

class Solution:
def canSeePersonsCount(self, heights: List[int]) -> List[int]:
stack = []
ans = []
n = len(heights)

for x in range(n-1, -1, -1):
ret = 0
while stack and heights[x] >= stack[-1]:
stack.pop()
ret += 1
ans.append(ret + (1 if stack else 0))
stack.append(heights[x])

return ans[::-1]

Explanation:

This solution uses a monotonic stack to track visible people, processing from right to left:

  1. For each person, we count how many people they can see
  2. We use a monotonic stack to maintain heights of people to the right
  3. We pop people shorter than current person (they’re visible) and count them
  4. If the stack is not empty after popping, the tallest remaining person is also visible
  5. Finally, we reverse the answer since we processed from right to left

Time Complexity: O(n) — Each height is pushed and popped at most once.
Space Complexity: O(n) — Stack size in worst case.

Edge Cases:

5. Verify Preorder Sequence in Binary Search Tree (LeetCode 255, Medium)

Problem:

Given an array of unique integers preorder, return true if it is the correct preorder traversal sequence of a binary search tree.

Example:

Python Solution:

class Solution:
def verifyPreorder(self, preorder: List[int]) -> bool:
stack = []
min_val = float("-inf")

for val in preorder:
while stack and stack[-1] < val:
min_val = max(min_val, stack.pop())

if val <= min_val:
return False

stack.append(val)
return True

Explanation:

This problem combines BST properties with monotonic stacks. The key insight:

  1. In a valid preorder traversal, once we start visiting a right subtree, all subsequent values must be greater than all ancestors of that right subtree
  2. We use a stack to simulate the traversal
  3. When we encounter a value greater than the stack top, we’re moving to a right subtree
  4. We keep track of a minimum value (min_val) that all future nodes must exceed
  5. If a value is less than or equal to this minimum, the preorder sequence is invalid

Time Complexity: O(n) — Each element is processed exactly once.
Space Complexity: O(n) — Stack size in worst case.

Edge Cases:

6. Remove K Digits (LeetCode 402, Medium)

Problem:

Given string num representing a non-negative integer, and an integer k, return the smallest possible integer after removing k digits from num.

Example:

Python Solution:

class Solution:
def removeKdigits(self, num, k):
st = []

for n in num:
while st and st[-1] > n and k:
st.pop()
k -= 1
if st or n != '0':
st.append(n)

if k:
st = st[0:-k]
return ''.join(st) or '0'

Explanation:

This problem is a perfect application of a monotonic increasing stack:

  1. We want to maintain the smallest possible number, which means keeping digits in increasing order
  2. When we encounter a digit smaller than the top of our stack, we remove the top (if we still have removals left)
  3. This greedy approach ensures we’re always removing the largest possible digit
  4. We handle leading zeros by only adding a digit if the stack is non-empty or the digit is not ‘0’
  5. If we still have removals left after processing all digits, we remove from the end
  6. If the final result is empty, we return “0”

Time Complexity: O(n) — Each digit is processed at most twice.
Space Complexity: O(n) — Stack size in worst case.

Edge Cases:

Tips and Tricks for Monotonic Stack Problems

Pattern Recognition:

Stack Type Selection:

Implementation Efficiency:

Circular Array Handling:

Greedy Decision Making:

Happy coding, and good luck with your next interview!

Follow me for more algorithm insights and Python solutions!