Rust for Algorithmic Problem-Solving

Monotonic Stack and Queue

A monotonic stack (Vec<usize>) or monotonic queue (VecDeque<usize>) maintains indices whose corresponding array values are strictly (or non-strictly) increasing or decreasing. Because every element is pushed onto the structure once and popped at most once, monotonic stacks and queues solve Next Greater Element, Largest Rectangle in a Histogram, and Sliding Window Minimum/Maximum problems in $O(n)$ amortized linear time!

Monotonic Decreasing Stack for Next Greater Element (`nums = [2, 1, 5, 3, 6]`):
  Step 0 (val=2): Stack = [ idx 0 (2) ]
  Step 1 (val=1): Stack = [ idx 0 (2), idx 1 (1) ]       (1 < 2, stays decreasing)
  Step 2 (val=5): 5 > 1 (pops idx 1 -> ans[1] = 5!)
                  5 > 2 (pops idx 0 -> ans[0] = 5!)
                  Stack = [ idx 2 (5) ]

Choosing the Right Monotonic Invariant

Target Query Stack / Queue Order (Bottom to Top) Pop Trigger Condition when Scanning nums[i]
Next Greater Element (to the right) Monotonic Decreasing (stack values decrease) Pop top while nums[i] > nums[top] (i is top's next greater!).
Next Smaller Element (to the right) Monotonic Increasing (stack values increase) Pop top while nums[i] < nums[top] (i is top's next smaller!).
Sliding Window Maximum Monotonic Decreasing VecDeque Pop back while nums[i] >= nums[back]; front is window max.
Sliding Window Minimum Monotonic Increasing VecDeque Pop back while nums[i] <= nums[back]; front is window min.

1. Next Greater Element in $O(n)$ Time

/// For each index `i`, returns the first value to the right of `i` that is strictly
/// greater than `nums[i]`, or `None` if no such element exists. Runs in O(n) time.
pub fn next_greater_elements(nums: &[i64]) -> Vec<Option<i64>> {
    let mut ans = vec![None; nums.len()];
    // Stores indices `idx` whose next greater element has not yet been seen:
    let mut stack: Vec<usize> = Vec::with_capacity(nums.len());

    for (i, &val) in nums.iter().enumerate() {
        while let Some(&top_idx) = stack.last() {
            if val > nums[top_idx] {
                stack.pop();
                ans[top_idx] = Some(val);
            } else {
                break;
            }
        }
        stack.push(i);
    }

    ans
}

2. Largest Rectangle in a Histogram in $O(n)$ Time

Using a monotonic increasing stack of bar indices, whenever a bar at index i is shorter than the bar at the top of the stack, the popped bar's maximum horizontal span is bounded on the right by i and on the left by the new top of the stack:

/// Computes the area of the largest axis-aligned rectangle in a histogram of bar heights
/// in O(n) time and O(n) space using a monotonic increasing stack.
pub fn largest_rectangle_area(heights: &[u64]) -> u64 {
    let mut stack: Vec<usize> = Vec::with_capacity(heights.len() + 1);
    let mut max_area = 0u64;

    // Iterate through all bars plus a virtual height-0 sentinel at `i == heights.len()`
    // to flush all remaining bars from the stack cleanly:
    for i in 0..=heights.len() {
        let cur_h = heights.get(i).copied().unwrap_or(0);

        while let Some(&top_idx) = stack.last() {
            if cur_h < heights[top_idx] {
                stack.pop();
                let h = heights[top_idx];
                let width = match stack.last() {
                    Some(&left_bound) => (i - left_bound - 1) as u64,
                    None => i as u64,
                };
                max_area = max_area.max(h * width);
            } else {
                break;
            }
        }
        stack.push(i);
    }

    max_area
}

fn main() {
    let nge = next_greater_elements(&[2, 1, 5, 3, 6]);
    assert_eq!(nge, vec![Some(5), Some(5), Some(6), Some(6), None]);

    let hist = [2, 1, 5, 6, 2, 3];
    let area = largest_rectangle_area(&hist);
    assert_eq!(area, 10); // Bars of height 5 and 6 span width 2 -> 5 * 2 = 10

    println!("Next greater: {nge:?}, Largest histogram rectangle: {area}");
}
Next greater: [Some(5), Some(5), Some(6), Some(6), None], Largest histogram rectangle: 10