001 Notes

Sequential Aggregation Professional Playbook

Overview and Reading Path

Sequential aggregation solves problems by scanning data in a deliberate order while maintaining a small summary of what has been seen.

Use this family when the answer comes from:

  • moving pointers through an array or string
  • growing and shrinking a contiguous window
  • comparing prefix summaries
  • counting frequencies or keyed states
  • merging sorted streams
  • maintaining a monotonic stack or queue

Read in this order:

  1. Identify the scan order.
  2. Define the maintained summary.
  3. State the invariant after each update.
  4. Decide when the answer updates.
  5. Pick the specific strategy.

Core Vocabulary

TermMeaning
Scan orderThe order in which elements are processed
SummaryThe compact information kept from processed elements
InvariantThe fact that stays true after each update
WindowA contiguous active range of the input
PointerAn index that marks a scan position or boundary
PrefixAggregate information from the beginning up to a position
CandidateAn item still able to affect the answer

Beginner Mental Model

Think of reading a long receipt from left to right.

You do not want to reread the receipt for every question. Instead, you keep a small notebook:

current total
items in current window
counts seen so far
best answer so far
useful candidates still waiting

Sequential aggregation is the skill of choosing what goes in that notebook.

The whole pattern is:

read next item
update the notebook
remove anything no longer valid
update the answer

From Brute Force To This Pattern

Brute force repeatedly asks the same question from scratch:

for every index:
    rescan earlier values
    recompute the same counts, sums, or candidates

Sequential aggregation changes the question:

After I scan one more item, how should my compact memory change?

That is the shift from repeated rescanning to one-pass memory maintenance.

What Information Becomes Reusable

Reusable information is the part of the past that can still affect the future:

  • a running sum
  • a prefix count
  • a frequency map
  • the left boundary of a valid window
  • unresolved indexes in a monotonic stack
  • candidate maximums in a monotonic queue

Everything else should be forgotten. A good sequential solution is often a controlled forgetting process.

What This Mental Model Prevents

This model prevents:

  • nested rescans that should be one pass
  • updating the answer before the window is valid
  • keeping expired elements in a queue
  • storing all history when only counts or candidates matter
  • using sliding window when negative numbers break monotonic shrinking

Small Visual Model

processed past        current       unseen future
[facts compressed] -> nums[i] -> [not inspected yet]
        |
        `-- summary must contain everything future steps need

What This Family Is / Is Not

Use this family when the input can be processed in a stable order and a compact summary is enough.

Do not use it when:

  • the answer requires exploring many choices
  • the problem has arbitrary graph reachability
  • the subproblem state repeats in a DP-like way
  • sorting or heap ordering is the real reason the solution works

Universal Template

initialize summary
initialize answer

for each scan position:
    add new information
    remove expired or invalid information
    restore invariant
    update answer

return answer

Core Mental Model

The key question is:

What do I know after processing this prefix, window, stream, or frontier?

Sequential aggregation is not about trying choices. It is about preserving the useful facts from a scan so the next answer update is cheap.

The universal shape is:

summary = empty
answer = neutral value

for item in scan order:
    add item to summary
    restore invariant
    update answer from summary

Every sequential solution has five contracts:

scan order:
    the exact order in which items become visible

summary:
    the facts remembered from processed items or the current window

enter rule:
    how the next item changes the summary

leave rule:
    whether and how old information is removed

answer update:
    the exact moment when the summary represents a valid candidate

How To Read This Family Visually

Use the same symbols throughout this section:

[]    active window, current prefix, unresolved stack, or deque contents
{}    stored summary: counts, prefix map, stack, deque, running value
^     current item, pointer, window boundary, or stream head
-->   scan movement, enter, leave, pop, or merge advance
x     expired, invalid, dominated, or no longer useful
==    equivalent prefix/summary relation
...   processed or unprocessed stream section

One-minute mental model:

see a one-way scan
    -> ask what the summary knows after each step
    -> add entering information and remove stale information
    -> update only when the invariant is true
    -> invariant: summary exactly describes the current prefix/window/frontier

Read every diagram as:

shape:
    what scan order is being used?
memory:
    what summary is kept instead of raw history?
action:
    what enters, leaves, or gets popped?
invariant:
    why is the answer update now valid?

ASCII Visual Map

Sequential aggregation should feel like a conveyor belt: information enters, stale information leaves if needed, and the answer is updated only when the summary is truthful.

prefix summary:

index:        0    1    2    3    4
value:        a    b    c    d    e
prefix:   0  | a |a+b|...|

range [l, r] is answered from two remembered prefix facts.
sliding window:

          left                 right
           v                     v
array:  [  .   .   .   .   .   .   .  ]
              <--- current window --->

step:
    add nums[right]
    while invalid: remove nums[left], left++
    update answer when window is valid
monotonic stack:

incoming value = 73

stack before:  [80, 75, 70, 68]
                         ^   ^
                         popped because 73 is warmer

stack after:   [80, 75, 73]

Popped items have found their next greater/warmer value.
monotonic queue for window maximum:

window indexes:      2   3   4   5
values:              1   9   4   7

deque indexes:           [3, 5]
deque values:            [9, 7]

Front is the best valid candidate; expired indexes leave from the front.
answer timing:

expand -> repair -> update

right enters summary
        |
        v
restore invariant
        |
        v
summary now describes a valid candidate
        |
        v
update answer

Scan Order

Most bugs start with an unclear scan order.

Problem shapeScan order
two pointersone or two monotonic pointer movements
sliding windowright pointer expands, left pointer repairs validity
prefix sumleft to right over indexes
merge techniquesmallest next item among sorted streams
monotonic stackleft to right or right to left depending on nearest relation
monotonic queueright pointer enters, expired left indexes leave
interval intersectionadvance the interval with smaller end

Problem Shape Diagrams

two pointers:

left                                      right
 v                                          v
[ 2 ][ 4 ][ 6 ][ 8 ][ 9 ][ 11 ][ 15 ][ 20 ]

Move one pointer using a proof that skipped pairs cannot win.
invariant: moved pointer cannot participate in a better skipped answer
sliding window:

              left              right
               v                  v
[ . ][ . ][   current window   ][ . ][ . ]

right expands; left shrinks until the invariant is restored.
invariant: summary describes exactly the current valid window
prefix sum:

array:    [a] [b] [c] [d] [e]
prefix: 0  |a|a+b|...|
             ^       ^
             l      r+1

range fact = prefix[r + 1] - prefix[l]
invariant: prefix[i] stores the aggregate before index i
merge technique:

A:  1   4   8
    ^
B:  2   3   9
    ^

Compare current heads, consume the smaller useful item.
invariant: consumed stream item is the next globally usable item
monotonic stack:

scan --->  5   3   4   2   6

stack before 6: [5, 4, 2]
pop dominated:     2, 4, 5
stack after 6:  [6]

Pops answer nearest-greater/smaller questions.
invariant: stack keeps only unresolved useful candidates
monotonic queue:

window values:  [1, 9, 4, 7]
deque values:      [9, 7]
                    ^
                  max

Back removes dominated values; front removes expired indexes.
invariant: deque front is the best valid candidate in the window
interval intersection:

A: [----]      [------]
B:    [---]  [----]
        ^^     ^^
     overlap  overlap

Advance the interval with the smaller end.
invariant: the smaller-ending interval cannot overlap future intervals more

The scan order is a proof that no skipped position needs to be reconsidered. If pointers can move backward, this family may not be the right model.

Summary Meaning

The summary is the compressed memory of the scan.

StrategySummary
two pointerscurrent endpoints and any running measure
sliding windowcounts, sum, distinct count, or matched characters in window
prefix sum + hashingrunning prefix plus previous prefix frequencies/indexes
counting + hashingfrequency table of processed items
merge techniquecurrent positions in each sorted stream
monotonic stackunresolved useful candidates in monotonic order
monotonic queuebest candidate indexes for the current window

A summary is complete when it contains everything needed for the next update. It is minimal when it does not store raw history that will never be queried.

Enter and Leave Mechanics

Every scanned item either accumulates forever, enters a window temporarily, or becomes a candidate that may be dominated.

prefix-only:
    information only enters

moving window:
    information enters on the right and leaves on the left

monotonic structure:
    new information removes dominated old candidates

merge scan:
    one stream position advances after the current comparison

Leaving is as important as entering. A sliding window count that never removes left-side characters is no longer describing the current window.

Invariant Before Answer Update

The invariant decides when the summary is safe to use.

ProblemInvariant before update
longest substring without repeatwindow has no duplicate characters
minimum size subarray sumwindow sum is at least target before shrinking update
subarray sum equals kmap stores prefixes before current index is counted
daily temperaturesstack is decreasing by temperature
sliding window maximumdeque front is inside window and greatest value
container with most watermoving shorter side cannot miss better area

Some problems update before repair, some after repair, and some during repair. Write that timing down before coding.

Prefix vs Window vs Candidate Memory

Do not force all scans into sliding windows.

NeedModel
range answer from difference of two earlier totalsprefix summary
contiguous valid region that can shrinksliding window
nearest greater/smaller unresolved itemmonotonic stack
min/max over moving fixed-size rangemonotonic queue
matching pairs/groups in processed valuescounting map
sorted lists consumed in ordermerge technique

The sign of numbers is a major clue. Variable-size sum windows rely on monotonic growth, so negative numbers often push the solution toward prefix sums or monotonic queues.

Universal Sequential Template

initialize summary and answer

for item or pointer position in scan order:
    incorporate entering information
    remove expired, invalid, or dominated information
    restore the invariant required by this strategy
    if the summary now represents a valid candidate:
        update the answer

return answer, transformed array, or final summary

Strategy-Specific Mental Model

StrategyMental modelMain risk
two pointerspointer movement discards impossible pairsmoving the wrong side
sliding windowwindow is repaired until validanswer update timing
prefix sum + hashingprevious prefixes complete current rangesstoring latest when earliest is needed
counting + hashingfrequency table represents processed portionstale counts
merge techniquesorted streams expose next usable itemadvancing wrong stream
monotonic stackstack keeps unresolved useful candidatespopping equal values incorrectly
monotonic queuedeque front is best valid window candidatenot expiring old indexes

Mini Traces

Trace 1: sliding window repair.

target: no duplicate characters

step 1:
    window = [a b c]
    counts = {a:1, b:1, c:1}
    invariant: window has no duplicates

step 2:
    add b -> [a b c b]
    counts = {a:1, b:2, c:1}
    invariant broken: b appears twice

step 3:
    remove from left until valid -> [c b]
    counts = {c:1, b:1}
    invariant: window has no duplicates again

Trace 2: monotonic stack.

scan temperatures:
70, 68, 73

step 1:
    stack = [70, 68]
    invariant: unresolved temperatures are decreasing

step 2:
    see 73
    pop 68, then pop 70
    invariant: popped indexes have found their next warmer day

step 3:
    push 73
    stack = [73]
    invariant: stack is decreasing again

Contrast Diagrams

Sliding window vs prefix sum:

sliding window:
    [current contiguous valid region]
       left ------------ right
    invariant: summary describes only the current window

prefix sum:
    prefix history: P0 P1 P2 P3 P4
    range = later prefix - earlier prefix
    invariant: map/array stores reusable facts about previous prefixes

Monotonic stack vs monotonic queue:

stack:
    unresolved candidates for nearest greater/smaller
    top changes as new items dominate old ones
    invariant: order is monotonic from bottom to top

queue:
    best candidates inside a moving window
    front expires when it leaves the window
    invariant: front is best valid candidate for the current window

Misclassification Checks

Use this family when one pass plus a small summary explains the solution. Be careful when:

SymptomBetter family may be
future choices branch and repeatState Space Exploration
the proof depends on sorted global discardOrdered Optimization
there are many independent range queriesRange Query & Indexed Aggregation
nodes and edges define the problemConnectivity Discovery
arithmetic identity is the main trickMathematical Numeric Reasoning

The clean test is: after processing item i, can you state exactly what the summary knows and why old information can be forgotten?

Detailed Translation Workflow

Translate every sequential problem in this order:

1. What is my scan order?
2. What enters the summary at each step?
3. What leaves the summary, if anything?
4. What invariant must be true before answer update?
5. What exact value updates the answer?

The most important distinction is whether the summary is prefix-only or a moving window:

prefix-only:
    information only accumulates

window:
    information enters on the right and leaves on the left

monotonic structure:
    worse candidates are discarded permanently

Strategy Map

StrategyUse whenMaintained invariant
Two Pointerstwo ends or slow/fast movement discards impossible casespointer movement never misses a better candidate
Sliding Windowanswer lives inside a contiguous rangewindow is valid, or becomes valid after shrinking
Prefix Sum + Hashingsubarray answers come from differences of prefixesmap stores previous prefix summaries
Counting + Hashingfrequencies decide matches, groups, or duplicatescounts reflect processed portion
Merge Techniquesorted streams can be consumed in orderoutput/answer uses the next smallest available item
Monotonic Stackneed next/previous greater or smaller itemstack stores useful candidates in monotonic order
Monotonic Queuemoving window needs min/max in O(1)front is best valid candidate

Translation Checklist

Ask these before coding:

Scan:
    What order do I process elements?

Summary:
    What facts must be kept?

Invariant:
    What is true after each step?

Update:
    When does the answer change?

Removal:
    Do elements ever leave the summary?

Problem Translation Table

Problem typeState / summaryUpdate ruleEngine
Container With Most Waterleft, right, best areamove shorter walltwo pointers
Two Sum IIsorted endpointscompare sum to targettwo pointers
Remove Duplicates From Sorted Arraywrite pointer, read pointerwrite unique valuesslow/fast pointer
Linked List Cycleslow and fast pointersadvance at different speedsslow/fast pointer
Longest Substring Without Repeatingwindow counts/setshrink until no duplicatesliding window
Minimum Size Subarray Sumwindow sumshrink while sum is enoughsliding window
Permutation in Stringwindow character countscompare fixed-size windowsliding window
Subarray Sum Equals Kprefix sum countscount prefix - kprefix sum + hash
Continuous Subarray Sumremainder first indexrepeat remainder with length >= 2prefix modulo hash
Product Except Selfleft product, right productcombine two passesprefix/suffix aggregation
Majority Element IIcandidates and countersBoyer-Moore cancellationcounting summary
Group Anagramscharacter-count keyappend word by signaturecounting + hashing
Merge Sorted Arraythree write pointerswrite larger tail valuemerge technique
Interval List Intersectionstwo interval pointersadvance earlier-ending intervalmerge technique
Daily Temperaturesdecreasing index stackpop warmer resolved daysmonotonic stack
Largest Rectangle in Histogramincreasing stackpop bars when boundary foundmonotonic stack
Sliding Window Maximumdecreasing dequeexpire old, pop worsemonotonic queue
Shortest Subarray With Sum At Least Kprefix dequemaintain increasing prefixesmonotonic queue

Canonical Examples

Two Pointers: Container With Most Water

Invariant:

All containers discarded by moving the shorter side cannot beat the current
candidate with that same side.

Skeleton:

def max_area(height):
    left = 0
    right = len(height) - 1
    best = 0

    while left < right:
        width = right - left
        best = max(best, width * min(height[left], height[right]))

        if height[left] < height[right]:
            left += 1
        else:
            right -= 1

    return best

Sliding Window: Longest Substring Without Repeating

Invariant:

s[left:right + 1] contains no repeated character.

Skeleton:

def length_of_longest_substring(s):
    seen = set()
    left = 0
    best = 0

    for right, ch in enumerate(s):
        while ch in seen:
            seen.remove(s[left])
            left += 1
        seen.add(ch)
        best = max(best, right - left + 1)

    return best

Prefix Sum + Hashing: Subarray Sum Equals K

Invariant:

counts[p] is how many earlier prefixes had sum p.

Skeleton:

from collections import defaultdict


def subarray_sum(nums, k):
    counts = defaultdict(int)
    counts[0] = 1
    prefix = 0
    ans = 0

    for value in nums:
        prefix += value
        ans += counts[prefix - k]
        counts[prefix] += 1

    return ans

Monotonic Stack: Daily Temperatures

Invariant:

The stack contains unresolved days with temperatures in decreasing order.

Skeleton:

def daily_temperatures(temperatures):
    ans = [0] * len(temperatures)
    stack = []

    for day, temp in enumerate(temperatures):
        while stack and temperatures[stack[-1]] < temp:
            prev = stack.pop()
            ans[prev] = day - prev
        stack.append(day)

    return ans

Monotonic Queue: Sliding Window Maximum

Invariant:

The deque stores indexes in decreasing value order, and the front is inside
the current window.

Skeleton:

from collections import deque


def max_sliding_window(nums, k):
    dq = deque()
    ans = []

    for i, value in enumerate(nums):
        while dq and dq[0] <= i - k:
            dq.popleft()
        while dq and nums[dq[-1]] <= value:
            dq.pop()
        dq.append(i)

        if i >= k - 1:
            ans.append(nums[dq[0]])

    return ans

Decision Rules

Use two pointers when sorted order or endpoint movement lets you discard a side. Use sliding window when the target object is contiguous and the window can be repaired by moving left. Use prefix sums when the answer for a subarray depends on prefix[j] - prefix[i]. Use counting when value frequencies are the real state. Use a monotonic stack when a future element resolves previous elements. Use a monotonic queue when a moving window needs the best candidate.

Common Failure Modes

  • Moving both pointers when only one side has been proven discardable.
  • Treating a non-contiguous subsequence problem as a sliding window.
  • Updating answer before restoring the window invariant.
  • Forgetting counts[0] = 1 in prefix-count problems.
  • Storing values instead of indexes in monotonic structures when expiration or distance matters.
  • Popping equal values incorrectly when duplicates change the answer.

Performance Notes

Most sequential aggregation solutions are O(n) because each item enters and leaves the maintained structure at most once.

Cache behavior is usually good because scans are linear. Hash maps and deques add overhead but avoid nested loops. SIMD is most realistic for numeric scans and prefix-style operations, but correctness usually comes from the invariant, not vectorization.

Cheat Sheet

Two pointers:
    What side can be discarded?

Sliding window:
    What makes the window valid?
    When do I shrink?

Prefix sum:
    What previous prefix would complete the answer?

Counting:
    What key groups equal states?

Monotonic stack:
    What does the current item resolve?

Monotonic queue:
    What candidates are expired or worse?

Extended Translation Notes

Sequential aggregation problems usually fail when the maintained summary is vague. Use this workflow to make the summary concrete before writing code.

Step 1: Decide the Scan Unit

Ask what one iteration consumes.

Examples:

SurfaceScan unit
arrayone index
stringone character
sorted intervalsone interval
sorted arraysone head from either stream
monotonic stackone current item that may resolve previous items
monotonic queueone index entering a moving window

The scan unit determines when data enters the maintained summary.

Step 2: Decide What Can Leave

Some scans only add information. Others also remove old information.

PatternAddRemove
Prefix sumcurrent valuenever
Counting prefixcurrent keynever
Sliding windowright valueleft values until valid
Fixed windownew right valueexpired left value
Monotonic stackcurrent indexworse unresolved indexes
Monotonic queuecurrent indexexpired indexes and worse back indexes

If elements leave the summary, write the removal rule before coding the answer update.

Step 3: State the Invariant

The invariant should be short enough to say out loud.

Good examples:

The window has no duplicate characters.
The map contains counts of prefixes before the current index.
The stack contains unresolved decreasing temperatures.
The deque front is the maximum inside the current window.

Bad examples:

The window is correct.
The stack has useful things.
The map stores stuff.

Vague invariants lead to off-by-one and update-order bugs.

Step 4: Place the Answer Update

There are three common placements:

after adding current item
after restoring validity
after resolving previous candidates

Examples:

  • Sliding window maximum updates after the window reaches size k.
  • Longest substring updates after duplicates are removed.
  • Daily Temperatures updates while popping unresolved colder days.
  • Prefix count problems update before inserting the current prefix if the subarray must end at the current index and start earlier.

Strategy Invariants

Two Pointers

Two pointers are about controlled discard.

The proof must explain why moving one pointer cannot remove the optimal answer.

Common movement rules:

SituationMove
sorted sum too smallmove left forward
sorted sum too largemove right backward
container areamove shorter wall
remove duplicatesadvance read, write only accepted values
linked-list cycleslow moves one, fast moves two

Sliding Window

Sliding window works when a contiguous range can be repaired by moving one side.

Flexible window:

expand right
while invalid:
    shrink left
update answer

Fixed window:

add right
if size > k:
    remove left
if size == k:
    update answer

Do not use sliding window for non-contiguous subsequences.

Prefix Sum + Hashing

Prefix hashing works because every subarray sum is a difference:

sum(i..j) = prefix[j + 1] - prefix[i]

For target k, when current prefix is p, a previous prefix p - k completes the answer.

The order matters:

answer += counts[current_prefix - k]
counts[current_prefix] += 1

This prevents counting an empty subarray unless the problem allows it.

Monotonic Stack

A monotonic stack stores candidates whose answer is not known yet.

When the current item makes top candidates useless or resolved, pop them.

Typical questions:

  • next greater element
  • previous smaller element
  • largest rectangle
  • remove digits for smallest number

The stack is usually not all previous items. It is the compressed set of previous items still capable of affecting the answer.

Monotonic Queue

A monotonic queue is a monotonic stack plus expiration.

It supports moving-window best values:

pop front while expired
pop back while worse than current
push current
front is the best valid candidate

Indexes are usually better than values because indexes tell you when an item expires.

Worked Problem Snapshots

Remove Duplicates From Sorted Array

State:

read = current scan index
write = next position for unique value

Invariant:

nums[0:write] contains the unique values from nums[0:read].

Trap:

Do not append to a new list if the problem requires in-place mutation.

Move Zeroes

State:

write = next position for a nonzero value

Invariant:

nums[0:write] contains all nonzero values seen so far in original order.

Trap:

Preserve relative order of nonzero elements.

Minimum Size Subarray Sum

State:

left, right, window_sum

Invariant:

After shrinking, window_sum is either below target or left is the smallest
start that was not yet proven too short.

Trap:

Only works with positive numbers because shrinking has monotonic behavior.

Permutation in String

State:

fixed-size window counts

Invariant:

The window length equals len(pattern) before comparing counts.

Trap:

Remove zero-count keys if dictionary equality is used.

Continuous Subarray Sum

State:

first_index_by_remainder

Invariant:

If a remainder repeats far enough apart, the subarray between them is divisible
by k.

Trap:

Store the first occurrence, not the latest, to maximize length.

Majority Element II

State:

two candidate slots and their counters

Invariant:

Cancellation removes triples of distinct values without changing possible
majorities over n/3.

Trap:

Verify candidates in a second pass.

Interval List Intersections

State:

i, j over two sorted interval lists

Invariant:

The interval with the earlier end cannot intersect future intervals from the
other list after this step.

Trap:

Advance by end time, not start time.

Largest Rectangle in Histogram

State:

increasing stack of bar indexes

Invariant:

Bars in the stack have not yet found a right boundary smaller than themselves.

Trap:

Add a sentinel zero height to flush the stack.

Shortest Subarray With Sum At Least K

State:

deque of prefix indexes with increasing prefix sums

Invariant:

Earlier worse prefixes are removed because a later smaller prefix is always
better.

Trap:

This is not a normal sliding window when numbers can be negative.

Dry Runs

Dry Run: Prefix Sum Count

Input:

nums = [1, 2, 1]
k = 3

Process:

start: prefix = 0, counts = {0: 1}, ans = 0

value 1:
    prefix = 1
    need = -2
    ans += 0
    counts[1] = 1

value 2:
    prefix = 3
    need = 0
    ans += 1
    counts[3] = 1

value 1:
    prefix = 4
    need = 1
    ans += 1
    counts[4] = 1

Answer:

2

Dry Run: Daily Temperatures

Input:

temperatures = [73, 74, 75, 71]

Process:

day 0, 73: stack = [0]
day 1, 74: pop 0, answer[0] = 1, stack = [1]
day 2, 75: pop 1, answer[1] = 1, stack = [2]
day 3, 71: no pop, stack = [2, 3]

Unresolved days keep answer 0.

Dry Run: Sliding Window Maximum

Input:

nums = [1, 3, 2, 5]
k = 2

Deque process:

i=0 value=1: dq=[0]
i=1 value=3: pop 0, dq=[1], answer 3
i=2 value=2: dq=[1,2], answer 3
i=3 value=5: expire 1, pop 2, dq=[3], answer 5

Answer:

[3, 3, 5]