A stack kept sorted answers “nearest element to the left/right that is smaller/greater” for every position in total. It is also the Cartesian tree in disguise, which explains why it shows up in RMQ, histograms and “sum over all subarrays” problems.

Nearest smaller / greater

vector<int> L(n), R(n), st;                    // L: previous strictly smaller, R: next smaller-or-equal
for (int i = 0; i < n; i++) {
    while (!st.empty() && a[st.back()] >= a[i]) R[st.back()] = i, st.pop_back();
    L[i] = st.empty() ? -1 : st.back();
    st.push_back(i);
}
for (int i : st) R[i] = n;

One pass gives both sides: an index is popped by its next smaller-or-equal element, and when pushed it reads its previous strictly smaller one. Flip the comparisons for “greater”. In general: for “previous”, read st.back() before pushing the current index; for “next”, assign the answer while popping.

WantDirectionPop while
previous smallerleft to righta[st.back()] >= a[i]
previous greaterleft to righta[st.back()] <= a[i]
next smallerright to left (or one pass, as above)a[st.back()] >= a[i]
next greaterright to left (or one pass)a[st.back()] <= a[i]

Ties: strict on one side and non-strict on the other means every subarray has exactly one designated minimum (the leftmost or rightmost occurrence). With strict on both sides, equal elements double count.

Contribution: sums over all subarrays

is the minimum of subarrays:

  • Sum of ranges (): the maximum version minus the minimum version.
  • Count subarrays where is the max and the subarray satisfies something else (for example length , or sum constraints): the range of valid is the rectangle , so count it with arithmetic or with small-to-large over the shorter side (iterating the shorter side gives total, the “divide at the maximum” trick).
  • Sum of gcds or ANDs over all subarrays: the set of distinct values ending at has size (Subarray Aggregates).

Largest rectangle in a histogram

The best rectangle with bar as its shortest spans , so the answer is . In one pass, with a sentinel:

ll largestRectangle(vector<ll> h) {
    h.push_back(-1); vector<int> st; ll best = 0;
    for (int i = 0; i < sz(h); i++) {
        while (!st.empty() && h[st.back()] >= h[i]) {
            ll ht = h[st.back()]; st.pop_back();
            int left = st.empty() ? -1 : st.back();
            best = max(best, ht * (i - left - 1));
        }
        st.push_back(i);
    }
    return best;
}

Maximal all-ones rectangle in a grid: for each row, heights = consecutive ones ending at that row; run the histogram per row, . Count all-ones subrectangles: per row, the number of rectangles with bottom-right corner at satisfies where is the previous strictly smaller height. in total.

Divide-and-conquer alternative: find the minimum bar; the answer either spans the whole range at that height or lies entirely to one side. With a sparse table for the minimum’s index this is , and if the Cartesian tree below is built first, since the recursion is that tree — slower to set up than the stack, but it’s the clearest explanation of why the Cartesian tree and the monotonic stack solve the same problem.

Trapping rain water

Water at is . Two pointers: move the side with the smaller current height, since its running maximum is the binding wall.

ll trap(const vector<int>& h) {
    int l = 0, r = sz(h) - 1, lm = 0, rm = 0; ll w = 0;
    while (l < r)
        if (h[l] < h[r]) lm = max(lm, h[l]), w += lm - h[l++];
        else rm = max(rm, h[r]), w += rm - h[r--];
    return w;
}

The stack version fills horizontal layers: pop a bottom, and the water above it is bounded by the new top and the current bar. 2D version: push all border cells into a min-heap and flood inward Dijkstra-style, since water on a grid escapes over the lowest point of the boundary. The current level is , and each cell traps . — the “water level” propagates inward exactly as Dijkstra’s distances do.

MethodTimeSpaceNote
brute force (max on each side per )the starting point
prefix/suffix maximaclearest
two pointersoptimal
monotonic stacklayer decomposition
VariantMethod
2D trapping rain waterpriority queue from the border inward (Dijkstra-like flood)
container with most watertwo pointers on
largest rectangle in a histogrammonotonic stack, see above
water with a leak / drainagemodify the boundary condition
maximum water after removing barsharder; DP or greedy depending on the constraints

Cartesian tree

The min-Cartesian tree of has the minimum at the root, the left part as the left subtree and the right part as the right subtree. The monotonic stack is its right spine during construction:

vector<int> par(n, -1), st;
for (int i = 0; i < n; i++) {
    int last = -1;
    while (!st.empty() && a[st.back()] > a[i]) last = st.back(), st.pop_back();
    if (last != -1) par[last] = i;
    if (!st.empty()) par[i] = st.back();
    st.push_back(i);
}
  • in the Cartesian tree, giving the / RMQ (RMQ).
  • “Divide at the minimum” recursions are traversals of this tree. Merging the smaller child into the larger gives for many “count subarrays whose minimum (or maximum) satisfies ” problems.
  • A treap is a Cartesian tree on (key, random priority).

More stack patterns

ProblemIdea
stock span, days until warmerprevious/next greater, distance
lexicographically smallest subsequence of length / remove digitspop while the top is larger and enough elements remain
smallest subsequence containing every distinct letter oncepop while top > current and the top occurs later
circular next greateriterate positions, push only in the first pass
visible people in a queue / count pairs that see each otherpop smaller, count pops, handle equal heights with counts
sum of of all subarraystwo stacks, contribution
convex hull (monotone chain), CHT with sorted slopessame pop-while-bad discipline with a cross product
matching brackets, longest valid parenthesesstack of indices with a sentinel base
next greater for queries offline / on treesstack along the DFS path with rollback

Debugging

The stack holds exactly the indices still “open”, whose answer hasn’t been determined. If a result is wrong, print the stack after each step on a 6-element example. Nearly every bug is < versus <=, or reading the top after pushing instead of before.