Monotonic Stack/Queue

Introduction

Monotonic stacks and queues are specialized data structures that maintain elements in either strictly increasing or decreasing order. They are particularly useful for problems involving finding next/previous greater/smaller elements or maintaining sliding window extremes.

Key Concepts

  • Monotonic Stack: LIFO structure maintaining order
  • Monotonic Queue: FIFO structure maintaining order
  • Increasing: Each new element ≥ top/back
  • Decreasing: Each new element ≤ top/back

Mathematics Behind Monotonic Structures

The core principle relies on order theory:

  • For a sequence S = {s₁, s₂, ..., sₙ}, we maintain a subsequence M = {m₁, m₂, ..., mₖ} where:
    • m₁ ≤ m₂ ≤ ... ≤ mₖ (increasing) or
    • m₁ ≥ m₂ ≥ ... ≥ mₖ (decreasing)
  • When processing element sᵢ:
    • Remove elements from M that violate the order condition
    • Add sᵢ to M
    • Each element is added/removed exactly once → O(n) time

Time and Space Complexity

  • Time Complexity: O(n) - Each element processed once
  • Space Complexity: O(n) - Worst case all elements stored
  • Operations:
    • Push: Amortized O(1)
    • Pop: O(1)
    • Get extremum: O(1)

Problem Examples

Easy Problems

1. Next Greater Element (Codeforces 911G)

Find the next greater element for each array element.

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

vector<int> nextGreater(vector<int>& nums) {
    int n = nums.size();
    vector<int> res(n, -1);
    stack<int> st;
    
    for (int i = 0; i < n; i++) {
        while (!st.empty() && nums[st.top()] < nums[i]) {
            res[st.top()] = nums[i];
            st.pop();
        }
        st.push(i);
    }
    return res;
}

int main() {
    vector<int> nums = {4, 5, 2, 25};
    vector<int> result = nextGreater(nums);
    
    for (int num : result) 
        cout << num << " "; // 5 25 25 -1
    return 0;
}

Input/Output Example

Input: [4, 5, 2, 25]
Output: [5, 25, 25, -1]

2. Stock Span Problem (Codeforces 5C)

Calculate span for each day where span is the number of consecutive days before it with price ≤ current price.

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

vector<int> stockSpan(vector<int>& prices) {
    int n = prices.size();
    vector<int> span(n, 1);
    stack<int> st; // Stores indices
    
    for (int i = 0; i < n; i++) {
        while (!st.empty() && prices[st.top()] <= prices[i]) {
            span[i] += span[st.top()];
            st.pop();
        }
        st.push(i);
    }
    return span;
}

int main() {
    vector<int> prices = {100, 80, 60, 70, 60, 75, 85};
    vector<int> spans = stockSpan(prices);
    
    for (int s : spans)
        cout << s << " "; // 1 1 1 2 1 4 6
    return 0;
}

Intermediate Problems

1. Sliding Window Maximum (Codeforces 940E)

Find maximum in each sliding window of size k.

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    vector<int> res;
    deque<int> dq;
    
    for (int i = 0; i < nums.size(); i++) {
        // Remove elements outside window
        if (!dq.empty() && dq.front() == i - k)
            dq.pop_front();
        
        // Maintain decreasing order
        while (!dq.empty() && nums[dq.back()] <= nums[i])
            dq.pop_back();
        
        dq.push_back(i);
        
        // Window is complete
        if (i >= k - 1)
            res.push_back(nums[dq.front()]);
    }
    return res;
}

int main() {
    vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
    int k = 3;
    vector<int> result = maxSlidingWindow(nums, k);
    
    for (int num : result)
        cout << num << " "; // 3 3 5 5 6 7
    return 0;
}

2. Largest Rectangle in Histogram (Codeforces 985F)

Find the largest rectangular area in a histogram.

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int largestRectangleArea(vector<int>& heights) {
    int n = heights.size();
    stack<int> st;
    int max_area = 0;
    
    for (int i = 0; i <= n; i++) {
        while (!st.empty() && (i == n || heights[st.top()] > heights[i])) {
            int h = heights[st.top()];
            st.pop();
            int w = st.empty() ? i : i - st.top() - 1;
            max_area = max(max_area, h * w);
        }
        st.push(i);
    }
    return max_area;
}

int main() {
    vector<int> heights = {2, 1, 5, 6, 2, 3};
    cout << largestRectangleArea(heights); // 10
    return 0;
}

Hard Problem

1. Constrained Subsequence Sum (Codeforces 1425F)

Find maximum sum of subsequence where no two elements are within k distance.

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

int constrainedSubsetSum(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> dp(n);
    deque<int> dq;
    int max_sum = nums[0];
    
    for (int i = 0; i < n; i++) {
        // Remove elements outside window
        while (!dq.empty() && dq.front() < i - k)
            dq.pop_front();
        
        dp[i] = nums[i];
        if (!dq.empty())
            dp[i] = max(dp[i], dp[dq.front()] + nums[i]);
        
        // Maintain decreasing order
        while (!dq.empty() && dp[dq.back()] <= dp[i])
            dq.pop_back();
        
        dq.push_back(i);
        max_sum = max(max_sum, dp[i]);
    }
    return max_sum;
}

int main() {
    vector<int> nums = {10, 2, -10, 5, 20};
    int k = 2;
    cout << constrainedSubsetSum(nums, k); // 37
    return 0;
}

Input/Output Example

Input: [10, 2, -10, 5, 20], k = 2
Output: 37 (10 + 5 + 20)