Keep a window whose ends only move forward. Each pointer moves at most times, so the scan is times the cost of updating the window.

The template

for (int l = 0, r = 0; r < n; r++) {
    add(a[r]);
    while (!valid()) remove(a[l++]);
    // [l, r] is the longest valid window ending at r
    best = max(best, r - l + 1);
    cnt += r - l + 1;                  // number of valid windows ending at r
}
  • Shortest valid window: while (valid()) { best = min(best, r - l + 1); remove(a[l++]); }.
  • Exactly = at most − at most (distinct values, odd numbers, sum with non-negative values).
  • Requirement: validity is monotone. If is valid, every sub-window is valid (or every super-window, for the shortest-window form).
ConditionMonotone?
sum , values yes
sum with negativesno: prefix sums + monotonic deque, or BIT
at most distinctyes
yes, with two monotonic deques
, , yes (the aggregate only moves one way as the window grows); use the two-stack queue for gcd
contains every required itemyes (shortest-window form)
sum exactly not in general: hash map of prefix sums
Window aggregateStructure
sum, counta running variable
distinct counta frequency map plus a counter
maximum / minimummonotonic deque, amortized
both max and mintwo deques
-th smallestPBDS or two heaps
anything non-invertible (gcd, matrix product, …)the two-stack queue (SWAG) below

Other two-pointer shapes

  • Opposite ends of a sorted array: two-sum, counting pairs with , container with most water. Three-sum: fix one element, two pointers on the rest, . General -sum: recurse by fixing one element and reducing to -sum, down to two-pointers at — nested fixed loops plus one two-pointer scan gives , and this is believed optimal for (3SUM-hardness, see fine-grained reductions).
  • Two sorted sequences: merge, intersect, count pairs with .
  • The pointer is a monotone function of : whenever the optimal for is non-decreasing in (it often is for convex costs), a pointer replaces a binary search. This is the same monotonicity that powers DP optimisations.
  • Fast and slow pointers: cycle detection in functional graphs (Floyd/Brent), middle of a list.

Monotonic deque: sliding window min/max

deque<int> dq;                                     // indices; values decreasing front to back
for (int i = 0; i < n; i++) {
    while (!dq.empty() && a[dq.back()] <= a[i]) dq.pop_back();
    dq.push_back(i);
    if (dq.front() <= i - k) dq.pop_front();
    if (i >= k - 1) out.push_back(a[dq.front()]);  // max of a[i-k+1..i]
}

An element dominated by a newer, larger one can never be the maximum again, so popping it is safe. Each index is pushed and popped once, total.

Uses:

  • DP over a window: (Monotone Queue Optimization); bounded knapsack in by running a window per residue class mod weight.
  • Shortest subarray with sum , negatives allowed: a deque of prefix indices with increasing ; pop the front while , recording the length.
  • Max sum with length in : a deque of the minimum over .
  • 2D sliding window max ( windows): run the 1D deque along rows, then along columns of the result. .

Two-stack queue (SWAG)

A queue built from two stacks supports any associative operation, even non-invertible ones (gcd, min, matrix product, function composition), in amortized per push, pop and query. Each stack stores the running aggregate. The back stack aggregates bottom to top; the front stack is filled by moving everything over when it’s empty and aggregates in reverse.

template<class T, class Op> struct SWAG {      // queue with fold of all elements, op associative
    Op op; T id;
    vector<T> fv, fa, bv; T ba;                // front values/aggregates, back values/aggregate
    SWAG(Op o, T e) : op(o), id(e), ba(e) {}
    void push(T x) { bv.push_back(x); ba = op(ba, x); }
    void pop() {
        if (fv.empty()) {                      // move back stack to front
            while (!bv.empty()) { T x = bv.back(); bv.pop_back(); fv.push_back(x); fa.push_back(fa.empty() ? x : op(x, fa.back())); }
            ba = id;
        }
        fv.pop_back(); fa.pop_back();
    }
    T query() const { return op(fa.empty() ? id : fa.back(), ba); }   // fold front-to-back
};

This is what you use for “two pointers with gcd / with matrix composition”, and for “Mo’s algorithm needs removals but the aggregate can’t remove” when the queries form a sliding window.

Min stack: store (value, min so far) per element, per op since a stack’s minimum only ever depends on a prefix of it. Min queue: two min stacks (in receives pushes, out serves pops; when out empties, move everything from in across, reversing order; the queue’s minimum is the smaller of the two stacks’ minimums) — each element moves from in to out exactly once, so amortized. For a sliding window specifically the monotonic deque above is simpler and has a better constant, but it needs to know which value is leaving (FIFO order only); the two-stack version works for any push/pop sequence.

SituationStructure
sliding window over an arraymonotonic deque
arbitrary push/pop, need the minimumtwo-stack minimum queue
LIFO onlyminimum stack
need arbitrary deletionmultiset,

The two-stack minimum queue is what makes Mo’s algorithm with rollback work for a non-invertible aggregate (min needs both add and remove; when remove is impossible, this stack-based structure with undo substitutes), and it’s the same trick behind simulating a full min-deque with two stacks.

When it fails

If the left end sometimes needs to move back, two pointers don’t apply. Alternatives:

  • binary search on the window length, when validity is monotone in length;
  • a segment tree or sparse table to query arbitrary windows;
  • divide and conquer on the array: count windows that cross the midpoint, and solve each half recursively;
  • Mo’s algorithm for offline arbitrary ranges.