Problem. Given bar heights each of width 1, find the largest axis-aligned rectangle fitting inside the histogram.

The key observation

Every maximal rectangle has some bar as its shortest bar. For bar , the widest rectangle with height extends from the previous smaller bar to the next smaller bar:

So the answer is , and the whole problem reduces to finding, for each bar, its previous and next smaller elements — a monotonic stack job.

The one-pass solution —

long long largestRectangle(vector<int> h) {
    h.push_back(-1);                                  // sentinel flushes the stack
    stack<int> st;
    long long best = 0;
    for (int i = 0; i < (int)h.size(); i++) {
        while (!st.empty() && h[st.top()] >= h[i]) {
            int height = h[st.top()]; st.pop();
            int left = st.empty() ? -1 : st.top();
            best = max(best, (long long)height * (i - left - 1));
        }
        st.push(i);
    }
    return best;
}

When bar pops bar , we know:

  • the next smaller than is (that is why it popped);
  • the previous smaller than is the new stack top.

So both boundaries are available exactly at the moment of popping — which is why one pass suffices.

The sentinel -1 at the end flushes everything remaining, removing a separate cleanup loop.

The stack invariant

The stack always holds indices with strictly increasing heights: the bars that are still “open” because nothing shorter has appeared to their right yet. Each index is pushed once and popped once, so the total work is despite the inner while.

Maximal rectangle in a binary matrix

Build, for each row, the histogram of consecutive 1s ending at that row, and run the histogram algorithm on each:

int maximalRectangle(vector<vector<int>>& g) {
    int n = g.size(), m = g[0].size(), best = 0;
    vector<int> h(m, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) h[j] = g[i][j] ? h[j] + 1 : 0;
        best = max(best, (int)largestRectangle(h));
    }
    return best;
}

— one of the neatest reductions in the subject: a 2D problem solved by instances of a 1D one, in linear total time.

ProblemStack use
Largest rectangle in a histogramthis page
Maximal rectangle of 1s in a matrixrow histograms
Largest square of 1sa simpler DP suffices
Trapping rain waterlayer decomposition
Sum of subarray minimumscount where each element is the minimum
Next greater elementdirectly
Stock spanprevious greater
Remove digits for the smallest numbergreedy with a stack
Maximum area under a skylinesame
Build a Cartesian tree in the stack is the right spine

Sum of subarray minimums

// each element contributes h[i] * (i - left[i]) * (right[i] - i)

Use strict on one side and non-strict on the other so that ties are counted exactly once. This “count the subarrays each element dominates” pattern turns an enumeration into .

Divide and conquer — the 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 index, this is — and if you build the Cartesian tree first, since the recursion is that tree.

Slower than the stack, but the divide-and-conquer view explains why the Cartesian tree and the monotonic stack are the same object.

Why it is worth knowing

It is the canonical monotonic-stack problem, and the “for each element, find the range where it is the minimum” framing solves an entire family of array problems. Once you see that framing, the stack code writes itself.

See also: Monotonic Stack · Trapping Rain Water · Range Minimum Query