Practice Problems

Here are the essential problems to master the Monotonic Stack pattern.

Easy Problems

1. Next Greater Element I

  • Brief: Find the next greater element for a subset of numbers.
  • Why this pattern?: We need to find the “next greater” value for multiple elements efficiently.
  • Key Insight: Use a map to store the result of the monotonic stack pre-processing.
  graph LR
    Input["Input: 4, 1, 2"] --> Stack
    Stack["Monotonic Stack"] --> Map["Map: 4->-1, 1->2, 2->-1"]
    Map --> Output["Output for subset"]
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var nextGreaterElement = function(nums1, nums2) {
    const map = new Map(); // stores value -> next greater
    const stack = [];

    for (let num of nums2) {
        while (stack.length > 0 && stack[stack.length - 1] < num) {
            map.set(stack.pop(), num);
        }
        stack.push(num);
    }

    return nums1.map(n => map.has(n) ? map.get(n) : -1);
};
class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        stack = []
        mapping = {}

        for num in nums2:
            while stack and num > stack[-1]:
                mapping[stack.pop()] = num
            stack.append(num)

        return [mapping.get(n, -1) for n in nums1]
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Map<Integer, Integer> map = new HashMap<>(); // map from x to next greater element of x
        Deque<Integer> stack = new ArrayDeque<>();

        for (int num : nums2) {
            while (!stack.isEmpty() && stack.peek() < num) {
                map.put(stack.pop(), num);
            }
            stack.push(num);
        }

        int[] res = new int[nums1.length];
        for (int i = 0; i < nums1.length; i++) {
            res[i] = map.getOrDefault(nums1[i], -1);
        }
        return res;
    }
}
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> map;
        stack<int> st;

        for (int num : nums2) {
            while (!st.empty() && st.top() < num) {
                map[st.top()] = num;
                st.pop();
            }
            st.push(num);
        }

        vector<int> res;
        for (int num : nums1) {
            res.push_back(map.count(num) ? map[num] : -1);
        }
        return res;
    }
};
func nextGreaterElement(nums1 []int, nums2 []int) []int {
    mp := make(map[int]int)
    var stack []int

    for _, num := range nums2 {
        for len(stack) > 0 && stack[len(stack)-1] < num {
            mp[stack[len(stack)-1]] = num
            stack = stack[:len(stack)-1]
        }
        stack = append(stack, num)
    }

    res := make([]int, len(nums1))
    for i, num := range nums1 {
        if val, ok := mp[num]; ok {
            res[i] = val
        } else {
            res[i] = -1
        }
    }
    return res
}
# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def next_greater_element(nums1, nums2)
    stack = []
    mapping = {}

    nums2.each do |num|
        while !stack.empty? && stack.last < num
            mapping[stack.pop] = num
        end
        stack.push(num)
    end

    nums1.map { |n| mapping.fetch(n, -1) }
end

Medium Problems

2. Daily Temperatures

  • Brief: Given daily temperatures, return how many days you have to wait for a warmer temperature.
  • Why this pattern?: Asking for “days until warmer” is equivalent to “distance to next greater element”.
  • Key Insight: Store indices in the stack to calculate the distance (current_index - popped_index).
  flowchart TD
    A["Index 0: 73"] --> Stack
    B["Index 1: 74"] -- "74 > 73" --> Pop0["Pop 0"]
    Pop0 -- "Dist = 1-0 = 1" --> Ans0["Ans[0] = 1"]
/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function(temperatures) {
    const n = temperatures.length;
    const res = new Array(n).fill(0);
    const stack = []; // indices

    for (let i = 0; i < n; i++) {
        while (stack.length > 0 && temperatures[i] > temperatures[stack[stack.length - 1]]) {
            const idx = stack.pop();
            res[idx] = i - idx;
        }
        stack.push(i);
    }
    return res;
};
class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        res = [0] * len(temperatures)
        stack = [] # indices

        for i, temp in enumerate(temperatures):
            while stack and temp > temperatures[stack[-1]]:
                idx = stack.pop()
                res[idx] = i - idx
            stack.append(i)

        return res
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] res = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();

        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                int idx = stack.pop();
                res[idx] = i - idx;
            }
            stack.push(i);
        }
        return res;
    }
}
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> res(n, 0);
        stack<int> st; // indices

        for (int i = 0; i < n; ++i) {
            while (!st.empty() && temperatures[i] > temperatures[st.top()]) {
                int idx = st.top();
                st.pop();
                res[idx] = i - idx;
            }
            st.push(i);
        }
        return res;
    }
};
func dailyTemperatures(temperatures []int) []int {
    n := len(temperatures)
    res := make([]int, n)
    var stack []int // indices

    for i, temp := range temperatures {
        for len(stack) > 0 && temp > temperatures[stack[len(stack)-1]] {
            idx := stack[len(stack)-1]
            stack = stack[:len(stack)-1]
            res[idx] = i - idx
        }
        stack = append(stack, i)
    }
    return res
}
# @param {Integer[]} temperatures
# @return {Integer[]}
def daily_temperatures(temperatures)
    res = Array.new(temperatures.length, 0)
    stack = [] # indices

    temperatures.each_with_index do |temp, i|
        while !stack.empty? && temp > temperatures[stack.last]
            idx = stack.pop
            res[idx] = i - idx
        end
        stack.push(i)
    end

    res
end

3. Next Greater Element II

  • Brief: Find the next greater element for each element in a circular array.
  • Why this pattern?: Classic next greater problem with a twist of circular traversal.
  • Key Insight: Iterate through the array twice (or use modulo) to simulate the circular nature.
  flowchart LR
    subgraph Circular["Circular Array"]
        A["1"] --> B["2"]
        B --> C["1"]
        C -.-> A
    end
    subgraph Result["Next Greater"]
        R1["1 -> 2"]
        R2["2 -> -1"]
        R3["1 -> 2"]
    end
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var nextGreaterElements = function(nums) {
    const n = nums.length;
    const res = new Array(n).fill(-1);
    const stack = [];

    // Iterate twice to handle circular nature
    for (let i = 0; i < 2 * n; i++) {
        const num = nums[i % n];
        while (stack.length > 0 && nums[stack[stack.length - 1]] < num) {
            res[stack.pop()] = num;
        }
        if (i < n) {
            stack.push(i);
        }
    }

    return res;
};
class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        n = len(nums)
        res = [-1] * n
        stack = []

        # Iterate twice to handle circular nature
        for i in range(2 * n):
            num = nums[i % n]
            while stack and nums[stack[-1]] < num:
                res[stack.pop()] = num
            if i < n:
                stack.append(i)

        return res
class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        Arrays.fill(res, -1);
        Deque<Integer> stack = new ArrayDeque<>();

        // Iterate twice to handle circular nature
        for (int i = 0; i < 2 * n; i++) {
            int num = nums[i % n];
            while (!stack.isEmpty() && nums[stack.peek()] < num) {
                res[stack.pop()] = num;
            }
            if (i < n) {
                stack.push(i);
            }
        }

        return res;
    }
}
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n, -1);
        stack<int> st;

        // Iterate twice to handle circular nature
        for (int i = 0; i < 2 * n; ++i) {
            int num = nums[i % n];
            while (!st.empty() && nums[st.top()] < num) {
                res[st.top()] = num;
                st.pop();
            }
            if (i < n) {
                st.push(i);
            }
        }

        return res;
    }
};
func nextGreaterElements(nums []int) []int {
    n := len(nums)
    res := make([]int, n)
    for i := range res {
        res[i] = -1
    }
    var stack []int

    // Iterate twice to handle circular nature
    for i := 0; i < 2*n; i++ {
        num := nums[i%n]
        for len(stack) > 0 && nums[stack[len(stack)-1]] < num {
            res[stack[len(stack)-1]] = num
            stack = stack[:len(stack)-1]
        }
        if i < n {
            stack = append(stack, i)
        }
    }

    return res
}
# @param {Integer[]} nums
# @return {Integer[]}
def next_greater_elements(nums)
    n = nums.length
    res = Array.new(n, -1)
    stack = []

    # Iterate twice to handle circular nature
    (0...(2 * n)).each do |i|
        num = nums[i % n]
        while !stack.empty? && nums[stack.last] < num
            res[stack.pop] = num
        end
        stack.push(i) if i < n
    end

    res
end

4. Online Stock Span

  • Brief: Design a class to calculate the span of a stock’s price for today.
  • Why this pattern?: The span is the count of consecutive days with price less than or equal to today’s price.
  • Key Insight: Use a decreasing monotonic stack storing (price, span) pairs.
  flowchart TD
    subgraph Stack["Monotonic Decreasing Stack"]
        S1["(100, 1)"]
        S2["(80, 1)"]
        S3["(60, 1)"]
    end
    New["New Price: 70"] --> Pop["Pop 60"]
    Pop --> Result["Span = 1 + 1 = 2"]
var StockSpanner = function() {
    this.stack = []; // [price, span]
};

/**
 * @param {number} price
 * @return {number}
 */
StockSpanner.prototype.next = function(price) {
    let span = 1;

    while (this.stack.length > 0 && this.stack[this.stack.length - 1][0] <= price) {
        span += this.stack.pop()[1];
    }

    this.stack.push([price, span]);
    return span;
};
class StockSpanner:
    def __init__(self):
        self.stack = []  # (price, span)

    def next(self, price: int) -> int:
        span = 1

        while self.stack and self.stack[-1][0] <= price:
            span += self.stack.pop()[1]

        self.stack.append((price, span))
        return span
class StockSpanner {
    private Deque<int[]> stack; // [price, span]

    public StockSpanner() {
        stack = new ArrayDeque<>();
    }

    public int next(int price) {
        int span = 1;

        while (!stack.isEmpty() && stack.peek()[0] <= price) {
            span += stack.pop()[1];
        }

        stack.push(new int[]{price, span});
        return span;
    }
}
class StockSpanner {
private:
    stack<pair<int, int>> st; // (price, span)

public:
    StockSpanner() {}

    int next(int price) {
        int span = 1;

        while (!st.empty() && st.top().first <= price) {
            span += st.top().second;
            st.pop();
        }

        st.push({price, span});
        return span;
    }
};
type StockSpanner struct {
    stack [][2]int // [price, span]
}

func Constructor() StockSpanner {
    return StockSpanner{stack: make([][2]int, 0)}
}

func (this *StockSpanner) Next(price int) int {
    span := 1

    for len(this.stack) > 0 && this.stack[len(this.stack)-1][0] <= price {
        span += this.stack[len(this.stack)-1][1]
        this.stack = this.stack[:len(this.stack)-1]
    }

    this.stack = append(this.stack, [2]int{price, span})
    return span
}
class StockSpanner
    def initialize()
        @stack = [] # [price, span]
    end

    # @param {Integer} price
    # @return {Integer}
    def next(price)
        span = 1

        while !@stack.empty? && @stack.last[0] <= price
            span += @stack.pop[1]
        end

        @stack.push([price, span])
        span
    end
end

5. Remove K Digits

  • Brief: Remove k digits from a number to make it the smallest possible.
  • Why this pattern?: We want an increasing sequence of digits for the smallest number.
  • Key Insight: Maintain an increasing monotonic stack; pop larger digits when a smaller digit arrives.
  flowchart LR
    Input["1432219, k=3"] --> Process
    Process --> Step1["Stack: 1"]
    Step1 --> Step2["Stack: 14 -> 13"]
    Step2 --> Step3["Stack: 12"]
    Step3 --> Output["Result: 1219"]
/**
 * @param {string} num
 * @param {number} k
 * @return {string}
 */
var removeKdigits = function(num, k) {
    const stack = [];

    for (const digit of num) {
        while (k > 0 && stack.length > 0 && stack[stack.length - 1] > digit) {
            stack.pop();
            k--;
        }
        stack.push(digit);
    }

    // Remove remaining k digits from the end
    while (k > 0) {
        stack.pop();
        k--;
    }

    // Remove leading zeros and build result
    const result = stack.join('').replace(/^0+/, '');
    return result || '0';
};
class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        stack = []

        for digit in num:
            while k > 0 and stack and stack[-1] > digit:
                stack.pop()
                k -= 1
            stack.append(digit)

        # Remove remaining k digits from the end
        stack = stack[:-k] if k else stack

        # Remove leading zeros and build result
        return ''.join(stack).lstrip('0') or '0'
class Solution {
    public String removeKdigits(String num, int k) {
        Deque<Character> stack = new ArrayDeque<>();

        for (char digit : num.toCharArray()) {
            while (k > 0 && !stack.isEmpty() && stack.peek() > digit) {
                stack.pop();
                k--;
            }
            stack.push(digit);
        }

        // Remove remaining k digits from the end
        while (k > 0) {
            stack.pop();
            k--;
        }

        // Build result in correct order
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) {
            sb.append(stack.pollLast());
        }

        // Remove leading zeros
        while (sb.length() > 0 && sb.charAt(0) == '0') {
            sb.deleteCharAt(0);
        }

        return sb.length() == 0 ? "0" : sb.toString();
    }
}
class Solution {
public:
    string removeKdigits(string num, int k) {
        string stack;

        for (char digit : num) {
            while (k > 0 && !stack.empty() && stack.back() > digit) {
                stack.pop_back();
                k--;
            }
            stack.push_back(digit);
        }

        // Remove remaining k digits from the end
        while (k > 0) {
            stack.pop_back();
            k--;
        }

        // Remove leading zeros
        size_t start = stack.find_first_not_of('0');
        return start == string::npos ? "0" : stack.substr(start);
    }
};
func removeKdigits(num string, k int) string {
    stack := []byte{}

    for i := 0; i < len(num); i++ {
        digit := num[i]
        for k > 0 && len(stack) > 0 && stack[len(stack)-1] > digit {
            stack = stack[:len(stack)-1]
            k--
        }
        stack = append(stack, digit)
    }

    // Remove remaining k digits from the end
    stack = stack[:len(stack)-k]

    // Remove leading zeros
    result := strings.TrimLeft(string(stack), "0")
    if result == "" {
        return "0"
    }
    return result
}
# @param {String} num
# @param {Integer} k
# @return {String}
def remove_kdigits(num, k)
    stack = []

    num.each_char do |digit|
        while k > 0 && !stack.empty? && stack.last > digit
            stack.pop
            k -= 1
        end
        stack.push(digit)
    end

    # Remove remaining k digits from the end
    stack = stack[0...-k] if k > 0

    # Remove leading zeros and build result
    result = stack.join.sub(/^0+/, '')
    result.empty? ? '0' : result
end

6. 132 Pattern

  • Brief: Find if there exists i < j < k such that nums[i] < nums[k] < nums[j].
  • Why this pattern?: Use a decreasing stack to track potential “3” (nums[j]) candidates.
  • Key Insight: Traverse from right to left, maintaining the maximum “2” (nums[k]) that was popped.
  flowchart TD
    subgraph Pattern["132 Pattern"]
        N1["nums[i] = 1"] --> N3["nums[j] = 3"]
        N3 --> N2["nums[k] = 2"]
    end
    Condition["1 < 2 < 3"]
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var find132pattern = function(nums) {
    const stack = [];
    let third = -Infinity; // The "2" in 132

    // Traverse from right to left
    for (let i = nums.length - 1; i >= 0; i--) {
        if (nums[i] < third) {
            return true; // Found "1" that is less than "2"
        }
        while (stack.length > 0 && stack[stack.length - 1] < nums[i]) {
            third = stack.pop(); // Update "2" (nums[k])
        }
        stack.push(nums[i]); // Push potential "3" (nums[j])
    }

    return false;
};
class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        stack = []
        third = float('-inf')  # The "2" in 132

        # Traverse from right to left
        for i in range(len(nums) - 1, -1, -1):
            if nums[i] < third:
                return True  # Found "1" that is less than "2"
            while stack and stack[-1] < nums[i]:
                third = stack.pop()  # Update "2" (nums[k])
            stack.append(nums[i])  # Push potential "3" (nums[j])

        return False
class Solution {
    public boolean find132pattern(int[] nums) {
        Deque<Integer> stack = new ArrayDeque<>();
        int third = Integer.MIN_VALUE; // The "2" in 132

        // Traverse from right to left
        for (int i = nums.length - 1; i >= 0; i--) {
            if (nums[i] < third) {
                return true; // Found "1" that is less than "2"
            }
            while (!stack.isEmpty() && stack.peek() < nums[i]) {
                third = stack.pop(); // Update "2" (nums[k])
            }
            stack.push(nums[i]); // Push potential "3" (nums[j])
        }

        return false;
    }
}
class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        stack<int> st;
        int third = INT_MIN; // The "2" in 132

        // Traverse from right to left
        for (int i = nums.size() - 1; i >= 0; --i) {
            if (nums[i] < third) {
                return true; // Found "1" that is less than "2"
            }
            while (!st.empty() && st.top() < nums[i]) {
                third = st.top(); // Update "2" (nums[k])
                st.pop();
            }
            st.push(nums[i]); // Push potential "3" (nums[j])
        }

        return false;
    }
};
func find132pattern(nums []int) bool {
    stack := []int{}
    third := math.MinInt32 // The "2" in 132

    // Traverse from right to left
    for i := len(nums) - 1; i >= 0; i-- {
        if nums[i] < third {
            return true // Found "1" that is less than "2"
        }
        for len(stack) > 0 && stack[len(stack)-1] < nums[i] {
            third = stack[len(stack)-1] // Update "2" (nums[k])
            stack = stack[:len(stack)-1]
        }
        stack = append(stack, nums[i]) // Push potential "3" (nums[j])
    }

    return false
}
# @param {Integer[]} nums
# @return {Boolean}
def find132pattern(nums)
    stack = []
    third = -Float::INFINITY # The "2" in 132

    # Traverse from right to left
    (nums.length - 1).downto(0) do |i|
        return true if nums[i] < third # Found "1" that is less than "2"

        while !stack.empty? && stack.last < nums[i]
            third = stack.pop # Update "2" (nums[k])
        end
        stack.push(nums[i]) # Push potential "3" (nums[j])
    end

    false
end

Hard Problems

7. Largest Rectangle in Histogram

  • Brief: Find the largest rectangle area possible in a histogram.
  • Why this pattern?: Each bar limits the rectangle width to the left and right until a smaller bar is encountered.
  • Key Insight: Use an increasing stack. When we pop a bar h, the bar at the new top is the “left smaller” and the current bar i is the “right smaller”. Width = right - left - 1.
  flowchart TD
    Bar["Pop Bar H"] --> Left["Left Boundary: New Top"]
    Bar --> Right["Right Boundary: Current Index i"]
    Left --> Width["Width = i - NewTop - 1"]
    Right --> Width
    Width --> Area["Area = H * Width"]
/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function(heights) {
    heights.push(0); // Add sentinel
    const stack = [];
    let maxArea = 0;

    for (let i = 0; i < heights.length; i++) {
        while (stack.length > 0 && heights[i] < heights[stack[stack.length - 1]]) {
            const h = heights[stack.pop()];
            // If stack is empty, it means h was the smallest so far, so width stretches from 0 to i
            const w = stack.length === 0 ? i : i - stack[stack.length - 1] - 1;
            maxArea = Math.max(maxArea, h * w);
        }
        stack.push(i);
    }

    return maxArea;
};
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights.append(0) # Sentinel
        stack = []
        max_area = 0

        for i, h in enumerate(heights):
            while stack and h < heights[stack[-1]]:
                height = heights[stack.pop()]
                width = i if not stack else i - stack[-1] - 1
                max_area = max(max_area, height * width)
            stack.append(i)

        return max_area
class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        Deque<Integer> stack = new ArrayDeque<>();
        int maxArea = 0;

        for (int i = 0; i <= n; i++) {
            int h = (i == n) ? 0 : heights[i]; // Sentinel
            while (!stack.isEmpty() && h < heights[stack.peek()]) {
                int height = heights[stack.pop()];
                int width = stack.isEmpty() ? i : i - stack.peek() - 1;
                maxArea = Math.max(maxArea, height * width);
            }
            stack.push(i);
        }
        return maxArea;
    }
}
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        heights.push_back(0); // Sentinel
        stack<int> st;
        int maxArea = 0;

        for (int i = 0; i < heights.size(); ++i) {
            while (!st.empty() && heights[i] < heights[st.top()]) {
                int h = heights[st.top()];
                st.pop();
                int w = st.empty() ? i : i - st.top() - 1;
                maxArea = max(maxArea, h * w);
            }
            st.push(i);
        }
        return maxArea;
    }
};
func largestRectangleArea(heights []int) int {
    heights = append(heights, 0) // Sentinel
    var stack []int
    maxArea := 0

    for i, h := range heights {
        for len(stack) > 0 && h < heights[stack[len(stack)-1]] {
            height := heights[stack[len(stack)-1]]
            stack = stack[:len(stack)-1]

            width := i
            if len(stack) > 0 {
                width = i - stack[len(stack)-1] - 1
            }

            if area := height * width; area > maxArea {
                maxArea = area
            }
        }
        stack = append(stack, i)
    }
    return maxArea
}
# @param {Integer[]} heights
# @return {Integer}
def largest_rectangle_area(heights)
    heights << 0 # Sentinel
    stack = []
    max_area = 0

    heights.each_with_index do |h, i|
        while !stack.empty? && h < heights[stack.last]
            height = heights[stack.pop]
            width = stack.empty? ? i : i - stack.last - 1
            max_area = [max_area, height * width].max
        end
        stack.push(i)
    end

    max_area
end

8. Trapping Rain Water

  • Brief: Given elevation heights, calculate how much water can be trapped after rain.
  • Why this pattern?: Water is trapped between taller bars; a monotonic stack helps find boundaries.
  • Key Insight: Use a decreasing stack. When a taller bar is found, calculate water trapped in the “valley” formed.
  flowchart TD
    subgraph Elevation["Elevation Map"]
        E1["0"] --- E2["1"] --- E3["0"] --- E4["2"]
    end
    subgraph Water["Water Trapped"]
        W["1 unit at index 2"]
    end
    E3 --> W
/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    const stack = [];
    let water = 0;

    for (let i = 0; i < height.length; i++) {
        while (stack.length > 0 && height[i] > height[stack[stack.length - 1]]) {
            const bottom = stack.pop();

            if (stack.length === 0) break;

            const left = stack[stack.length - 1];
            const width = i - left - 1;
            const boundedHeight = Math.min(height[i], height[left]) - height[bottom];
            water += width * boundedHeight;
        }
        stack.push(i);
    }

    return water;
};
class Solution:
    def trap(self, height: List[int]) -> int:
        stack = []
        water = 0

        for i, h in enumerate(height):
            while stack and h > height[stack[-1]]:
                bottom = stack.pop()

                if not stack:
                    break

                left = stack[-1]
                width = i - left - 1
                bounded_height = min(h, height[left]) - height[bottom]
                water += width * bounded_height

            stack.append(i)

        return water
class Solution {
    public int trap(int[] height) {
        Deque<Integer> stack = new ArrayDeque<>();
        int water = 0;

        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int bottom = stack.pop();

                if (stack.isEmpty()) break;

                int left = stack.peek();
                int width = i - left - 1;
                int boundedHeight = Math.min(height[i], height[left]) - height[bottom];
                water += width * boundedHeight;
            }
            stack.push(i);
        }

        return water;
    }
}
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        int water = 0;

        for (int i = 0; i < height.size(); ++i) {
            while (!st.empty() && height[i] > height[st.top()]) {
                int bottom = st.top();
                st.pop();

                if (st.empty()) break;

                int left = st.top();
                int width = i - left - 1;
                int boundedHeight = min(height[i], height[left]) - height[bottom];
                water += width * boundedHeight;
            }
            st.push(i);
        }

        return water;
    }
};
func trap(height []int) int {
    stack := []int{}
    water := 0

    for i, h := range height {
        for len(stack) > 0 && h > height[stack[len(stack)-1]] {
            bottom := stack[len(stack)-1]
            stack = stack[:len(stack)-1]

            if len(stack) == 0 {
                break
            }

            left := stack[len(stack)-1]
            width := i - left - 1
            boundedHeight := min(h, height[left]) - height[bottom]
            water += width * boundedHeight
        }
        stack = append(stack, i)
    }

    return water
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}
# @param {Integer[]} height
# @return {Integer}
def trap(height)
    stack = []
    water = 0

    height.each_with_index do |h, i|
        while !stack.empty? && h > height[stack.last]
            bottom = stack.pop

            break if stack.empty?

            left = stack.last
            width = i - left - 1
            bounded_height = [h, height[left]].min - height[bottom]
            water += width * bounded_height
        end
        stack.push(i)
    end

    water
end

9. Maximal Rectangle

  • Brief: Find the largest rectangle containing only 1s in a binary matrix.
  • Why this pattern?: Transform each row into a histogram and apply the largest rectangle algorithm.
  • Key Insight: Build heights row by row; if current cell is ‘1’, increment height, else reset to 0.
  flowchart TD
    subgraph Matrix["Binary Matrix"]
        R1["1 0 1 0 0"]
        R2["1 0 1 1 1"]
        R3["1 1 1 1 1"]
    end
    subgraph Heights["Heights at Row 3"]
        H["3 1 3 2 2"]
    end
    Matrix --> Heights
    Heights --> Histogram["Apply Largest Rectangle"]
/**
 * @param {character[][]} matrix
 * @return {number}
 */
var maximalRectangle = function(matrix) {
    if (matrix.length === 0) return 0;

    const cols = matrix[0].length;
    const heights = new Array(cols).fill(0);
    let maxArea = 0;

    for (const row of matrix) {
        // Update heights
        for (let j = 0; j < cols; j++) {
            heights[j] = row[j] === '1' ? heights[j] + 1 : 0;
        }
        // Calculate max rectangle for this histogram
        maxArea = Math.max(maxArea, largestRectangleArea(heights.slice()));
    }

    return maxArea;
};

function largestRectangleArea(heights) {
    heights.push(0);
    const stack = [];
    let maxArea = 0;

    for (let i = 0; i < heights.length; i++) {
        while (stack.length > 0 && heights[i] < heights[stack[stack.length - 1]]) {
            const h = heights[stack.pop()];
            const w = stack.length === 0 ? i : i - stack[stack.length - 1] - 1;
            maxArea = Math.max(maxArea, h * w);
        }
        stack.push(i);
    }

    return maxArea;
}
class Solution:
    def maximalRectangle(self, matrix: List[List[str]]) -> int:
        if not matrix:
            return 0

        cols = len(matrix[0])
        heights = [0] * cols
        max_area = 0

        for row in matrix:
            # Update heights
            for j in range(cols):
                heights[j] = heights[j] + 1 if row[j] == '1' else 0

            # Calculate max rectangle for this histogram
            max_area = max(max_area, self.largestRectangleArea(heights[:]))

        return max_area

    def largestRectangleArea(self, heights: List[int]) -> int:
        heights.append(0)
        stack = []
        max_area = 0

        for i, h in enumerate(heights):
            while stack and h < heights[stack[-1]]:
                height = heights[stack.pop()]
                width = i if not stack else i - stack[-1] - 1
                max_area = max(max_area, height * width)
            stack.append(i)

        return max_area
class Solution {
    public int maximalRectangle(char[][] matrix) {
        if (matrix.length == 0) return 0;

        int cols = matrix[0].length;
        int[] heights = new int[cols];
        int maxArea = 0;

        for (char[] row : matrix) {
            // Update heights
            for (int j = 0; j < cols; j++) {
                heights[j] = row[j] == '1' ? heights[j] + 1 : 0;
            }
            // Calculate max rectangle for this histogram
            maxArea = Math.max(maxArea, largestRectangleArea(heights.clone()));
        }

        return maxArea;
    }

    private int largestRectangleArea(int[] heights) {
        Deque<Integer> stack = new ArrayDeque<>();
        int maxArea = 0;
        int n = heights.length;

        for (int i = 0; i <= n; i++) {
            int h = (i == n) ? 0 : heights[i];
            while (!stack.isEmpty() && h < heights[stack.peek()]) {
                int height = heights[stack.pop()];
                int width = stack.isEmpty() ? i : i - stack.peek() - 1;
                maxArea = Math.max(maxArea, height * width);
            }
            stack.push(i);
        }

        return maxArea;
    }
}
class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if (matrix.empty()) return 0;

        int cols = matrix[0].size();
        vector<int> heights(cols, 0);
        int maxArea = 0;

        for (const auto& row : matrix) {
            // Update heights
            for (int j = 0; j < cols; ++j) {
                heights[j] = row[j] == '1' ? heights[j] + 1 : 0;
            }
            // Calculate max rectangle for this histogram
            maxArea = max(maxArea, largestRectangleArea(heights));
        }

        return maxArea;
    }

private:
    int largestRectangleArea(vector<int> heights) {
        heights.push_back(0);
        stack<int> st;
        int maxArea = 0;

        for (int i = 0; i < heights.size(); ++i) {
            while (!st.empty() && heights[i] < heights[st.top()]) {
                int h = heights[st.top()];
                st.pop();
                int w = st.empty() ? i : i - st.top() - 1;
                maxArea = max(maxArea, h * w);
            }
            st.push(i);
        }

        return maxArea;
    }
};
func maximalRectangle(matrix [][]byte) int {
    if len(matrix) == 0 {
        return 0
    }

    cols := len(matrix[0])
    heights := make([]int, cols)
    maxArea := 0

    for _, row := range matrix {
        // Update heights
        for j := 0; j < cols; j++ {
            if row[j] == '1' {
                heights[j]++
            } else {
                heights[j] = 0
            }
        }
        // Calculate max rectangle for this histogram
        area := largestRectangleArea(append([]int{}, heights...))
        if area > maxArea {
            maxArea = area
        }
    }

    return maxArea
}

func largestRectangleArea(heights []int) int {
    heights = append(heights, 0)
    var stack []int
    maxArea := 0

    for i, h := range heights {
        for len(stack) > 0 && h < heights[stack[len(stack)-1]] {
            height := heights[stack[len(stack)-1]]
            stack = stack[:len(stack)-1]

            width := i
            if len(stack) > 0 {
                width = i - stack[len(stack)-1] - 1
            }

            if area := height * width; area > maxArea {
                maxArea = area
            }
        }
        stack = append(stack, i)
    }

    return maxArea
}
# @param {Character[][]} matrix
# @return {Integer}
def maximal_rectangle(matrix)
    return 0 if matrix.empty?

    cols = matrix[0].length
    heights = Array.new(cols, 0)
    max_area = 0

    matrix.each do |row|
        # Update heights
        (0...cols).each do |j|
            heights[j] = row[j] == '1' ? heights[j] + 1 : 0
        end
        # Calculate max rectangle for this histogram
        max_area = [max_area, largest_rectangle_area(heights.dup)].max
    end

    max_area
end

def largest_rectangle_area(heights)
    heights << 0
    stack = []
    max_area = 0

    heights.each_with_index do |h, i|
        while !stack.empty? && h < heights[stack.last]
            height = heights[stack.pop]
            width = stack.empty? ? i : i - stack.last - 1
            max_area = [max_area, height * width].max
        end
        stack.push(i)
    end

    max_area
end

Recommended Study Order

  1. Start with the Basics: Solve Next Greater Element I to understand the core mechanism of pushing and popping.
  2. Handle Circular Arrays: Try Next Greater Element II to learn the double-pass technique.
  3. Move to Index Tracking: Solve Daily Temperatures where storing indices is crucial.
  4. Design Problems: Practice Online Stock Span for real-world applications.
  5. String Manipulation: Work through Remove K Digits to see stack-based greedy approaches.
  6. Pattern Recognition: Tackle 132 Pattern for a challenging twist on monotonic stacks.
  7. Master Histogram: Solve Largest Rectangle in Histogram to understand boundary calculations.
  8. Water Trapping: Try Trapping Rain Water for a classic interview favorite.
  9. 2D Extension: Complete Maximal Rectangle to combine histogram techniques with matrix traversal.