Skip to main content
๐Ÿ“šBefore you start
Make sure you're comfortable with Arrays & Strings and Two Pointers first.

The Sliding Window Technique โ€” A Complete Reference Guide

From foundational theory to advanced applications in DSA, Data Engineering, ML, NLP/LLMs, and Computer Vision.

How to read this guide: Every concept is explained at two levels โ€” a conceptual layer (build intuition) and a technical layer (implement it). Callout boxes hold analogies (๐ŸชŸ), expert takeaways (๐Ÿ’ก), warnings (โš ๏ธ), and do/don't markers (โœ…/โŒ). All Python is typed, commented, and runnable on Python 3.10+.


๐Ÿ“Œ SECTION 1: What Is Sliding Window?โ€‹

1.1 Definition (Technical)โ€‹

The Sliding Window is an algorithmic technique that maintains a contiguous sub-range (the "window") over a linear sequence โ€” an array, string, or stream โ€” and processes the sequence by incrementally moving the window's boundaries rather than recomputing over the full range at every step.

Formally: given a sequence S of length n, a window is an interval [L, R] with 0 โ‰ค L โ‰ค R < n. The technique advances L and/or R monotonically (never backward), maintaining an aggregate state (sum, count, frequency map, max/min, etc.) that is updated in O(1) or O(log k) as elements enter at R and exit at L.

The defining property is incremental reuse: the answer for window [L, R+1] is derived from the answer for [L, R] by adding one element, not by re-scanning.

1.2 Core Intuition (Conceptual)โ€‹

Most naive solutions to subarray/substring problems recompute overlapping work. Consider summing every window of size k: windows [0,k-1] and [1,k] share k-1 elements. A brute-force approach re-adds those shared elements every time. The sliding window recognizes the overlap and only accounts for the difference โ€” the element that left and the element that joined.

๐ŸชŸ The Camera Pan Analogy: Imagine filming a long parade through a fixed-width camera frame. As the parade moves, your frame slides along โ€” you never reshoot the entire scene, only update what enters and exits the frame. This is exactly how a sliding window avoids redundant computation.

๐ŸชŸ The Conveyor Belt Analogy: Picture a quality inspector watching a fixed stretch of a conveyor belt. Items roll in on the right and roll off on the left. To keep a running tally of, say, "defects currently visible," the inspector adds a defect when one appears on the right and subtracts one when it rolls off the left โ€” never recounting the whole belt.

1.3 Problem Class โ€” What Triggers Its Useโ€‹

Sliding window applies when all of these hold:

  1. The data is linear and ordered (array, string, stream, time-series).
  2. You are asked about contiguous subranges (subarrays / substrings), not arbitrary subsets.
  3. The metric of interest is incrementally maintainable โ€” you can update it cheaply on enter/exit (sum, count, frequency, running max/min via deque, distinct-count via hashmap).
  4. There is a monotonic relationship that lets pointers move forward without backtracking (e.g., growing the window can only increase the sum for non-negative arrays; shrinking can only reduce a violation).

Typical phrasings: "longest/shortest/maximum/minimum contiguous subarray/substring such that โ€ฆ", "window of size k", "at most / exactly K distinct โ€ฆ".

1.4 Why It Beats Brute Forceโ€‹

ApproachIdeaTimeSpace
Brute forceEnumerate all subarrays, recompute metric each timeO(nยฒ) or O(nยทk)O(1)
Prefix sumsPrecompute cumulative sums, then O(1) range queriesO(n) build + O(n) or O(nยฒ) queryO(n)
Sliding windowMaintain running aggregate, move boundariesO(n)O(1)โ€“O(k)

The sliding window collapses an O(nยฒ) family of problems into O(n) by exploiting the overlap between adjacent windows. Each element is added exactly once and removed at most once, giving amortized O(1) work per element.

๐Ÿ’ก Expert Takeaway: The sliding window is not one algorithm โ€” it is a state-maintenance discipline. The hard part is rarely moving the pointers; it is defining a window aggregate that can be updated in O(1) on both entry and exit, and identifying the invariant that keeps pointers monotonic. If you cannot maintain the metric incrementally, sliding window will not help โ€” reach for prefix sums, a heap, or a different structure instead.


๐Ÿ“Œ SECTION 2: Types of Sliding Windowsโ€‹

2.1 Fixed-Size Windowโ€‹

Conceptual: The window width k is constant. Both boundaries advance together in lockstep. You slide one element at a time: one enters, one leaves.

Technical: Right pointer drives the loop; the left is implicitly R - k + 1. State is updated by state += in - out. Used when the problem fixes the subrange length up front ("subarray of size k", "every k consecutive readings").

2.2 Variable / Dynamic Windowโ€‹

Conceptual: The window grows and shrinks to satisfy a constraint. You expand the right edge greedily; when the window violates a condition (too many distinct chars, sum too large, duplicate present), you contract the left edge until it is valid again.

Technical: Two nested-feeling but amortized-linear pointers. Outer loop advances R; an inner while advances L to restore the invariant. Because L only ever moves forward, total pointer movement is โ‰ค 2n โ†’ O(n). Used for "longest/shortest subarray satisfying a predicate."

There are two common shapes:

  • Longest valid window: expand freely, shrink only when invalid, record max length after each step.
  • Shortest valid window: expand until valid, then shrink as far as possible while still valid, recording min length.

2.3 Multi-Pointer / Multi-Windowโ€‹

Conceptual: More than two indices coordinate โ€” e.g., a "at most K" minus "at most K-1" trick uses two windows; some problems track a second lagging pointer or maintain parallel windows over multiple sequences.

Technical: Common patterns:

  • atMost(K) โˆ’ atMost(Kโˆ’1) to count subarrays with exactly K of something (distinct integers, odd numbers, sum). Each atMost call is a variable window; the subtraction isolates "exactly."
  • Twin pointers over two arrays (merge-like scans), or a monotonic deque acting as an internal secondary structure for O(1) max/min.

2.4 Decision Criteriaโ€‹

QuestionAnswer โ†’ Variant
Is the subrange length given and fixed?โ†’ Fixed-size
Are you optimizing length subject to a constraint (longest/shortest valid)?โ†’ Variable
Do you need running max/min inside the window?โ†’ Variable/Fixed + monotonic deque
Do you need count of subarrays with an exact property?โ†’ Multi-window (atMost(K) โˆ’ atMost(Kโˆ’1))
Does the constraint break monotonicity (e.g., negative numbers with a sum target)?โ†’ Sliding window may not apply โ€” consider prefix sums + hashmap

2.5 Comparison Table (Type ร— Use Case ร— Complexity)โ€‹

Window TypeCanonical Use CasePointer MovementTimeSpace
Fixed-sizeMax/avg sum of size-k subarray; rolling statsL and R togetherO(n)O(1)โ€“O(k)
Variable (longest)Longest substring without repeats; longest subarray with sum โ‰ค SR expands, L shrinks on violationO(n)O(k) or O(alphabet)
Variable (shortest)Minimum window substring; smallest subarray with sum โ‰ฅ SR expands, L shrinks while validO(n)O(k) or O(alphabet)
Fixed/Var + monotonic dequeSliding window maximum/minimumR expands, deque prunesO(n)O(k)
Multi-window (atMost trick)Subarrays with exactly K distincttwo variable passesO(n)O(k)

๐Ÿ’ก Expert Takeaway: ~80% of interview and production sliding-window problems reduce to one of three templates: fixed-size, longest-variable, shortest-variable. Memorize these three skeletons cold. The remaining hard cases are almost always "add a monotonic deque for O(1) extremum" or "apply the atMost(K) โˆ’ atMost(Kโˆ’1) counting identity."


๐Ÿ“Œ SECTION 3: Algorithms & Codeโ€‹

3.1 Pseudocodeโ€‹

Fixed-size window

function fixed_window(A, k):
state โ† aggregate(A[0 .. k-1]) # build first window
best โ† state
for R from k to n-1:
state โ† state + A[R] โˆ’ A[R-k] # add incoming, drop outgoing
best โ† better(best, state)
return best

Variable window โ€” longest valid

function longest_valid(A):
L โ† 0
best โ† 0
init window_state
for R from 0 to n-1:
add A[R] to window_state
while window_state violates constraint:
remove A[L] from window_state
L โ† L + 1
best โ† max(best, R โˆ’ L + 1)
return best

Variable window โ€” shortest valid

function shortest_valid(A, target):
L โ† 0
best โ† +โˆž
init window_state
for R from 0 to n-1:
add A[R] to window_state
while window_state satisfies constraint:
best โ† min(best, R โˆ’ L + 1)
remove A[L] from window_state
L โ† L + 1
return best if best < +โˆž else 0

Sliding window maximum (monotonic deque)

function window_max(A, k):
dq โ† empty deque of indices # holds indices, values decreasing
result โ† []
for R from 0 to n-1:
while dq not empty and A[dq.back] โ‰ค A[R]: dq.pop_back()
dq.push_back(R)
if dq.front โ‰ค R โˆ’ k: dq.pop_front() # drop out-of-window index
if R โ‰ฅ k โˆ’ 1: result.append(A[dq.front])
return result

3.2 Annotated Python โ€” Classic DSA Problemโ€‹

# Problem: Maximum Sum Subarray of Size K (Fixed Window)
# Time: O(n) | Space: O(1)

def max_sum_subarray(arr: list[int], k: int) -> int:
"""
Return the maximum sum of any contiguous subarray of length k.

Fixed sliding window: instead of recomputing each window's sum from
scratch (which would be O(n*k)), we subtract the outgoing element and
add the incoming element as the window slides โ€” O(1) per step.
"""
if k <= 0 or k > len(arr):
raise ValueError("k must satisfy 1 <= k <= len(arr)")

window_sum: int = sum(arr[:k]) # Sum of the first window โ€” O(k), done once
max_sum: int = window_sum

# R is the index of the incoming element; R - k is the outgoing one.
for R in range(k, len(arr)):
window_sum += arr[R] - arr[R - k] # Slide: +incoming, -outgoing
max_sum = max(max_sum, window_sum)

return max_sum


# --- quick check ---
assert max_sum_subarray([2, 1, 5, 1, 3, 2], 3) == 9 # [5,1,3]

Complexity: One pass over n elements, O(1) work each โ†’ O(n) time. Only two integers held โ†’ O(1) space.


3.3 Annotated Python โ€” Variable Window (Longest Substring Without Repeats)โ€‹

# Problem: Longest Substring Without Repeating Characters (Variable Window)
# Time: O(n) | Space: O(min(n, alphabet))

def longest_unique_substring(s: str) -> int:
"""
Length of the longest substring with all-distinct characters.

Expand R to include s[R]. If that character is already in the window,
shrink from L until the duplicate is removed. The window [L, R] is
always duplicate-free, so its length is a candidate answer.
"""
last_seen: dict[str, int] = {} # char -> most recent index
L: int = 0
best: int = 0

for R, ch in enumerate(s):
# If we've seen ch inside the current window, jump L past its last occurrence.
if ch in last_seen and last_seen[ch] >= L:
L = last_seen[ch] + 1
last_seen[ch] = R
best = max(best, R - L + 1)

return best


assert longest_unique_substring("abcabcbb") == 3 # "abc"
assert longest_unique_substring("bbbbb") == 1

Complexity: Each index visited once by R; L only advances โ†’ O(n) time. The map holds at most one entry per distinct character โ†’ O(min(n, |ฮฃ|)) space.

โš ๏ธ Subtle bug to avoid: The guard last_seen[ch] >= L matters. Without it, a duplicate that lives outside the current window would wrongly drag L backward, breaking the monotonic-pointer invariant. Pointers in a sliding window must never move backward.


3.4 Annotated Python โ€” ML Preprocessing (Rolling Feature Extraction)โ€‹

# Problem: Rolling-window features for time-series ML (fixed window)
# Time: O(n) | Space: O(k) for the max/min deques, O(1) for mean/std state

from collections import deque
from math import sqrt

def rolling_features(series: list[float], k: int) -> list[dict[str, float]]:
"""
Compute rolling mean, std, max, and min over a fixed window of size k.

- mean/variance use Welford-style incremental sums (running sum and
running sum of squares) -> O(1) update.
- max/min use monotonic deques -> amortized O(1) update.

Returns one feature dict per fully-formed window (len = n - k + 1).
Designed for feature engineering where each row must summarize the
PAST k observations without leaking future data.
"""
if k <= 0 or k > len(series):
raise ValueError("k must satisfy 1 <= k <= len(series)")

out: list[dict[str, float]] = []
run_sum: float = 0.0
run_sq: float = 0.0
max_dq: deque[int] = deque() # indices, values decreasing
min_dq: deque[int] = deque() # indices, values increasing

for R, x in enumerate(series):
# --- incremental mean/variance state ---
run_sum += x
run_sq += x * x

# --- maintain monotonic deques for O(1) window max/min ---
while max_dq and series[max_dq[-1]] <= x:
max_dq.pop()
max_dq.append(R)
while min_dq and series[min_dq[-1]] >= x:
min_dq.pop()
min_dq.append(R)

L = R - k + 1 # left edge of the current window
if L > 0: # an element just left the window
out_val = series[L - 1]
run_sum -= out_val
run_sq -= out_val * out_val
# Evict indices that fell out of the window on the left.
if max_dq[0] < L:
max_dq.popleft()
if min_dq[0] < L:
min_dq.popleft()

if R >= k - 1: # window fully formed
mean = run_sum / k
# population variance; clamp tiny negatives from float error
var = max(run_sq / k - mean * mean, 0.0)
out.append({
"mean": mean,
"std": sqrt(var),
"max": series[max_dq[0]],
"min": series[min_dq[0]],
})

return out


feats = rolling_features([1, 2, 3, 4, 5], k=3)
assert feats[0]["mean"] == 2.0 and feats[0]["max"] == 3

Complexity: Single pass; mean/std updates are O(1), deque pushes/pops are amortized O(1) โ†’ O(n) time, O(k) space (deques bounded by window width).

๐Ÿ’ก Expert Takeaway: In production ML pipelines, sliding windows over time-series data must account for data-leakage boundaries โ€” your window must never include future data relative to your prediction target. Note above that each feature row summarizes only past observations [L, R] and is emitted at index R. Always validate window alignment (and label-timing) before model training, and be explicit about whether the window is trailing (causal) or centered (non-causal, leaks the future).


3.5 Annotated Python โ€” NLP / LLM Context Windowing (Token Chunking with Overlap)โ€‹

# Problem: Chunk a token stream into overlapping context windows (fixed + stride)
# Time: O(n) | Space: O(n / stride * window) for the emitted chunks

def chunk_tokens(
tokens: list[int],
window: int,
stride: int,
) -> list[list[int]]:
"""
Split a long token sequence into overlapping fixed-size windows โ€”
the standard preprocessing step for feeding long documents to a
context-limited transformer (e.g., RAG chunking, long-doc QA).

`window` = max context length the model accepts.
`stride` = how far the window advances each step. overlap = window - stride,
which preserves cross-boundary context so information isn't split
exactly at a chunk edge.
"""
if window <= 0 or stride <= 0:
raise ValueError("window and stride must be positive")
if stride > window:
raise ValueError("stride > window would skip tokens (gaps between chunks)")

chunks: list[list[int]] = []
L: int = 0
n: int = len(tokens)

while L < n:
R = min(L + window, n) # window covers tokens[L:R]
chunks.append(tokens[L:R])
if R == n: # reached the end; stop to avoid dup tail chunks
break
L += stride # slide forward by stride (keeps overlap)

return chunks


# 10 tokens, window=4, stride=3 -> overlap of 1 token between chunks
assert chunk_tokens(list(range(10)), window=4, stride=3) == [
[0, 1, 2, 3], [3, 4, 5, 6], [6, 7, 8, 9],
]

Complexity: Each token is copied into O(window/stride) chunks; with fixed overlap ratio this is O(n) time and O(n) space for the emitted chunks.

๐Ÿ’ก Expert Takeaway: Transformer self-attention is itself a (soft, weighted) sliding-window idea taken to its limit โ€” full attention lets every token "see" every other token in O(nยฒ). To scale to long contexts, models restrict attention to a local window (e.g., Longformer's sliding-window attention, Mistral's sliding-window attention) so each token attends only to its w neighbors, dropping cost to O(nยทw). The chunking above is the data-level sliding window; windowed attention is the architecture-level one. Same principle, two layers of the stack.


๐Ÿ“Œ SECTION 4: Domain Applicationsโ€‹

DomainApplication ExampleWindow Type
DSAMax-sum size-k subarray; longest substring w/o repeats; min window substringFixed & Variable
Data EngineeringStream processing, tumbling/hopping/session windows, rolling aggregationsFixed & Session
MLTime-series feature engineering, signal smoothing, spectrogram framingFixed (trailing)
NLP / LLMContext windows, token chunking for RAG, sliding-window attentionFixed + stride
Computer VisionObject detection by scanning sub-regions (pre-CNN) โ†’ modern strided use2-D fixed

4.1 DSAโ€‹

The native habitat: subarray/substring optimization and counting. Nearly every "contiguous + optimize/count" problem is a window problem. (Full worked set in Section 7.)

4.2 Data Engineeringโ€‹

Stream processors (Flink, Kafka Streams, Spark Structured Streaming) formalize windowing:

  • Tumbling window โ€” fixed, non-overlapping (e.g., counts per 1-minute bucket).
  • Hopping / sliding window โ€” fixed size, advances by a smaller step โ†’ overlapping (e.g., 5-min window every 1 min).
  • Session window โ€” variable; closes after an inactivity gap. This is a dynamic window driven by event-time gaps rather than a fixed width.

๐Ÿ’ก Expert Takeaway: In distributed streaming, the real complexity isn't the window logic โ€” it's watermarks and late-arriving events. A window can't emit its final result until you're confident no more events for that interval will arrive. Sliding-window state is bounded by window size ร— key cardinality, so wide windows over high-cardinality keys are a classic memory blowup. Cap retention with watermarks and allowed-lateness.

4.3 Machine Learningโ€‹

  • Feature engineering: rolling mean/std/min/max/quantiles as lag features (see ยง3.4).
  • Signal processing: framing audio into overlapping frames before FFT (STFT / spectrograms) is a fixed window + hop length โ€” identical to token chunking.
  • Smoothing: moving averages, Savitzkyโ€“Golay filters operate over a sliding window.
  • Online learning / concept drift: train/evaluate on a sliding window of recent data so the model tracks a changing distribution.

4.4 NLP / LLMโ€‹

  • Chunking for RAG: split long docs into overlapping windows so retrieval units fit the embedding/model context (see ยง3.5).
  • Context window: the model's max token span is literally a fixed window over the conversation; long chats get truncated or summarized as they slide out.
  • Windowed / local attention: Longformer, BigBird, Mistral restrict attention to a local band to make long sequences tractable.
  • N-gram features: classic NLP n-grams are fixed sliding windows over tokens.

4.5 Computer Visionโ€‹

  • Pre-CNN detection: the sliding-window detector scanned a fixed-size box across the image at multiple scales, running a classifier (e.g., HOG + SVM for pedestrians, Violaโ€“Jones for faces) at each position โ€” a 2-D fixed window.
  • Modern use: convolution is a learned sliding window (a kernel slides with a stride). Explicit sliding windows persist in patch extraction (ViT patchify), anchor generation, and non-max suppression over strided proposals.

๐Ÿ’ก Expert Takeaway: The pre-CNN sliding-window detector was killed by cost: exhaustively classifying every window at every scale is O(positions ร— scales ร— classifier-cost). CNNs won partly because convolution shares computation across overlapping windows (the same intuition as 1-D incremental reuse), and region-proposal methods (R-CNN family) replaced brute-force scanning with a small set of candidate windows.


๐Ÿ“Œ SECTION 5: Expert Insights & Pitfallsโ€‹

5.1 Common Mistakes (โ‰ฅ5)โ€‹

  1. โŒ Moving a pointer backward. The entire O(n) guarantee rests on L and R being monotonic. A duplicate-handling bug (see ยง3.3) that lets L decrease silently reintroduces O(nยฒ) behavior or wrong answers.
  2. โŒ Forgetting to update window state on exit. Adding the incoming element but not subtracting the outgoing one corrupts the aggregate. Every enter must have a matching exit for fixed windows.
  3. โŒ Applying sliding window when monotonicity is broken. "Subarray with sum exactly S" over an array containing negatives is not a sliding-window problem โ€” shrinking no longer monotonically reduces the sum. Use prefix sums + hashmap instead.
  4. โŒ Off-by-one in window length. The window [L, R] has length R - L + 1, not R - L. This trips up min/max-length tracking constantly.
  5. โŒ Emitting results before the window is full (fixed-size). You must gate output on R >= k - 1.
  6. โŒ Recomputing the aggregate inside the loop (e.g., calling sum(window) each step). That silently reverts you to O(nยทk) โ€” the exact thing the technique exists to avoid.
  7. โŒ Data leakage in ML windows. Using a centered window (which includes future points) for a causal forecasting feature leaks the target.

5.2 Optimization Tipsโ€‹

  • โœ… Use a collections.deque for O(1) window max/min (monotonic deque). A naive max(window) each step is O(k) โ†’ O(nยทk) overall; the deque makes it amortized O(1) โ†’ O(n).
  • โœ… Track counts, not the whole window, when you need "distinct elements" or "frequency" โ€” a hashmap with a distinct counter updates in O(1) per step and avoids re-scanning.
  • โœ… Prefer incremental sums / sum-of-squares (Welford) for rolling mean/variance instead of recomputing.
  • โœ… Use the atMost(K) โˆ’ atMost(Kโˆ’1) identity to convert hard "exactly K" counting into two easy "at most K" windows.
  • โœ… Batch/vectorize in ML: pandas.Series.rolling(k), numpy stride tricks (np.lib.stride_tricks.sliding_window_view), or scipy.signal framing are C-optimized โ€” use them over hand-rolled loops in hot paths.
  • โœ… Fuse enter/exit updates into a single expression state += A[R] - A[R-k] to minimize branching.

5.3 Edge Cases (always test)โ€‹

Edge caseCorrect behavior
Empty array/string (n == 0)Return identity (0, "", or None) โ€” don't index.
Single element (n == 1)Window of size 1; ensure loop still runs.
k > n (window larger than array)Either raise, or return a sentinel โ€” decide and document. Never let arr[:k] silently give a short window and pretend it's size k.
k == nExactly one window โ€” the whole array.
k <= 0Invalid; raise ValueError.
All-equal / all-negative valuesMonotonicity assumptions may flip (esp. with negatives + sum targets).
Duplicates at the boundaryThe >= L guard (ยง3.3) must scope "seen" to the current window.

๐Ÿ’ก Expert Takeaway (per-section box): The single highest-leverage habit is writing the window invariant as a comment before you code โ€” e.g., "[L, R] always contains at most K distinct chars." Every pointer move then has one job: restore the invariant. Bugs in sliding-window code are almost always invariant violations, not arithmetic errors.


๐Ÿ“Œ SECTION 6: Pattern Recognitionโ€‹

6.1 Trigger Keywordsโ€‹

Scan the problem statement for these โ€” they strongly signal a window:

  • "contiguous subarray / substring"
  • "subarray/substring of size k" โ†’ fixed
  • "longest / shortest / maximum-length / minimum-length" + a constraint โ†’ variable
  • "at most K / exactly K / at least K" (distinct, odd, vowels, โ€ฆ) โ†’ variable / multi-window
  • "without repeating", "containing all", "minimum window" โ†’ variable
  • "rolling / moving / running" (average, sum, max) โ†’ fixed (streaming/ML)
  • "consecutive" elements/days/characters

If you see "subsequence" (non-contiguous) instead of "subarray/substring" โ†’ not sliding window (likely DP).

6.2 Decision Tree (text-based)โ€‹

Is the data linear (array / string / stream)?
โ”‚
โ”œโ”€ NO โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Not a sliding-window problem.
โ”‚
โ””โ”€ YES
โ”‚
Are we asked about a CONTIGUOUS range (subarray/substring)?
โ”‚
โ”œโ”€ NO (subset / subsequence) โ”€โ–บ Consider DP / two-pointer-on-sorted / hashing.
โ”‚
โ””โ”€ YES
โ”‚
Is the window LENGTH fixed/given (k)?
โ”‚
โ”œโ”€ YES โ”€โ–บ FIXED-SIZE WINDOW
โ”‚ โ”‚
โ”‚ Need running max/min inside window?
โ”‚ โ”œโ”€ YES โ”€โ–บ fixed window + MONOTONIC DEQUE
โ”‚ โ””โ”€ NO โ”€โ–บ plain fixed window (running sum/count)
โ”‚
โ””โ”€ NO (length is what we optimize/derive)
โ”‚
Can the metric be maintained incrementally AND is it monotonic
(growing worsens/only-improves a constraint predictably)?
โ”‚
โ”œโ”€ NO โ”€โ–บ prefix sums + hashmap (e.g., sum==S with negatives)
โ”‚
โ””โ”€ YES
โ”‚
Do we want the BEST length, or a COUNT of subarrays?
โ”‚
โ”œโ”€ best LONGEST valid โ”€โ–บ expand R; shrink L only on violation; track max len
โ”œโ”€ best SHORTEST valid โ”€โ–บ expand R until valid; shrink L while valid; track min len
โ””โ”€ COUNT with EXACTLY K โ”€โ–บ atMost(K) โˆ’ atMost(Kโˆ’1) (two variable windows)
TechniqueWhen it appliesRelationship to Sliding Window
Two PointersPairs/partitions, often on sorted data or from both ends (e.g., 2-sum sorted, container-with-water)Sliding window is a special case of two pointers where both move forward and maintain a window aggregate. Two-pointer is broader (pointers can start at opposite ends and converge).
Divide & ConquerProblem splits into independent subproblems, combine results (merge sort, max subarray via D&C)D&C recomputes across the whole range recursively (O(n log n)); sliding window's incremental reuse is usually O(n) and simpler when applicable.
Dynamic ProgrammingOverlapping subproblems with optimal substructure; often non-contiguous or 2-D state (LIS, edit distance, knapsack)Use DP when the answer depends on non-contiguous choices or when no monotonic window invariant exists. Some window problems (e.g., max subarray) have both a DP form (Kadane) and a window/prefix form.
Prefix Sums / HashmapRange-sum queries, "sum == S" with negativesThe go-to fallback when sliding window breaks due to lost monotonicity.

๐Ÿ’ก Expert Takeaway: "Two pointers" and "sliding window" are often conflated. Rule of thumb: if both indices march forward and you maintain an aggregate over the span between them, call it a sliding window. If the pointers start at opposite ends and converge (typically after sorting), it's the broader two-pointer pattern. Naming it correctly steers you to the right template fast.


๐Ÿ“Œ SECTION 7: Practice Problem Setโ€‹

7.1 Ten Categorized Problems (Easy โ†’ Hard)โ€‹

#Problem (LeetCode)DifficultyWindow TypeKey Insight (one line)
1Maximum Average Subarray I (LC 643)EasyFixedSlide a size-k window; track max running sum, divide once at the end.
2Contains Duplicate II (LC 219)EasyFixedKeep a hashset of the last k indices; a repeat inside the window is the answer.
3Longest Substring Without Repeating Characters (LC 3)MediumVariable (longest)Shrink L past a duplicate's last position; window stays unique.
4Max Consecutive Ones III (LC 1004)MediumVariable (longest)Longest window with โ‰ค K zeros; shrink when zero-count exceeds K.
5Fruit Into Baskets (LC 904)MediumVariable (longest)Longest subarray with โ‰ค 2 distinct values via a frequency map.
6Minimum Size Subarray Sum (LC 209)MediumVariable (shortest)Expand until sum โ‰ฅ target, then shrink while still โ‰ฅ target.
7Permutation in String (LC 567)MediumFixedFixed window = len(pattern); compare frequency counts for a match.
8Subarrays with K Different Integers (LC 992)HardMulti-windowexactly(K) = atMost(K) โˆ’ atMost(Kโˆ’1).
9Sliding Window Maximum (LC 239)HardFixed + monotonic dequeDeque of decreasing values; front is always the window max.
10Minimum Window Substring (LC 76)HardVariable (shortest)Expand to cover all target chars; contract greedily to minimize length.

7.2 One Fully Worked Solution โ€” Minimum Window Substring (LC 76)โ€‹

Problem: Given strings s and t, return the shortest substring of s that contains every character of t (including multiplicities). Return "" if none exists.

# Problem: Minimum Window Substring (LC 76) โ€” Variable (shortest) window
# Time: O(|s| + |t|) | Space: O(|ฮฃ|) (bounded by distinct chars in t)

from collections import Counter

def min_window(s: str, t: str) -> str:
"""
Shortest substring of s containing all chars of t (with multiplicity).

Strategy (shortest-valid variable window):
1. Expand R to include chars until the window is 'valid'
(covers every required char with enough count).
2. Once valid, contract L as far as possible while STILL valid,
recording the shortest valid window seen.
"""
if not s or not t or len(s) < len(t):
return ""

need: Counter[str] = Counter(t) # required char -> required count
missing: int = len(t) # total chars still needed (with multiplicity)

L: int = 0
best_len: int = float("inf") # type: ignore[assignment]
best_start: int = 0

for R, ch in enumerate(s): # R = right edge (incoming char)
# If ch is still needed (count > 0), consuming it reduces 'missing'.
if need[ch] > 0:
missing -= 1
need[ch] -= 1 # ch is now inside the window
# (may go negative = surplus of ch)

# When missing == 0, the window [L, R] is VALID: it covers all of t.
while missing == 0:
# Record if this valid window is the shortest so far.
if R - L + 1 < best_len:
best_len = R - L + 1
best_start = L

# Try to shrink from the left. The char at L is leaving.
left_ch = s[L]
need[left_ch] += 1 # we give back left_ch to the 'need' pool
# If need[left_ch] becomes positive, we NOW lack it -> window breaks.
if need[left_ch] > 0:
missing += 1
L += 1 # advance left edge (monotonic, never back)

return "" if best_len == float("inf") else s[best_start:best_start + best_len]


# --- checks ---
assert min_window("ADOBECODEBANC", "ABC") == "BANC"
assert min_window("a", "a") == "a"
assert min_window("a", "aa") == ""

Line-by-line reasoning

  1. Guard clauses: empty inputs or |s| < |t| can't yield a valid window โ†’ return "" early.
  2. need = Counter(t): how many of each char the window must contain. A positive value means "still required"; a non-positive value means "have enough (or surplus)."
  3. missing = len(t): total outstanding requirement counting multiplicity. The window is valid exactly when missing == 0.
  4. Expand loop (for R, ch โ€ฆ): we always grow the window by one char per iteration โ€” R is monotonic.
  5. if need[ch] > 0: missing -= 1: we only decrement missing when the incoming char was genuinely still needed. If need[ch] was already โ‰ค 0, this char is surplus and doesn't reduce the requirement.
  6. need[ch] -= 1: record that one more ch now sits inside the window (can go negative โ†’ surplus).
  7. while missing == 0: the window is valid; enter the contraction phase to find the shortest valid window with this R.
  8. Record best: R - L + 1 is the current window length (note the +1 โ€” ยง5.3 edge case). Save start + length if it's a new minimum.
  9. need[left_ch] += 1: the char at L is about to leave, so we return it to the "need" pool.
  10. if need[left_ch] > 0: missing += 1: if giving it back makes it required again, the window will no longer be valid after we drop it โ†’ bump missing, which ends the while.
  11. L += 1: advance the left edge โ€” forward only, preserving the O(n) guarantee.
  12. Return: reconstruct the best window, or "" if none was ever valid.

Why O(|s| + |t|): Building need is O(|t|). In the main pass, R moves |s| times and L moves at most |s| times total (monotonic), each doing O(1) work โ†’ O(|s|). Space is one counter over distinct characters โ†’ O(|ฮฃ|).


๐Ÿ“Œ APPENDIX: Quick-Reference Cheat Sheetโ€‹

A. The Three Core Templatesโ€‹

# 1) FIXED-SIZE
state = build(A[:k]); best = state
for R in range(k, n):
state += A[R] - A[R-k]
best = better(best, state)

# 2) LONGEST VALID (variable)
L = 0; best = 0
for R in range(n):
add(A[R])
while invalid(): # restore invariant
remove(A[L]); L += 1
best = max(best, R - L + 1)

# 3) SHORTEST VALID (variable)
L = 0; best = INF
for R in range(n):
add(A[R])
while valid():
best = min(best, R - L + 1)
remove(A[L]); L += 1

B. Complexity At A Glanceโ€‹

PatternTimeSpace
Fixed sum/countO(n)O(1)
Fixed + distinct/frequencyO(n)O(k)
Variable (longest/shortest)O(n)O(min(n,|ฮฃ|))
Window max/min (monotonic deque)O(n)O(k)
Exactly-K count (atMost trick)O(n)O(k)
Brute force (baseline to beat)O(nยฒ) / O(nยทk)O(1)

C. Trigger Words โ†’ Templateโ€‹

You seeโ€ฆReach forโ€ฆ
"size k", "every k", "rolling/moving"Fixed
"longest โ€ฆ such that", "without repeating"Longest variable
"shortest / minimum window โ€ฆ containing"Shortest variable
"max/min of each window"Fixed + deque
"count subarrays with exactly K โ€ฆ"atMost(K) โˆ’ atMost(Kโˆ’1)
"sum == S" with negativesโŒ not window โ†’ prefix sums + hashmap

D. Invariant Checklist Before You Submitโ€‹

  • โœ… Pointers only ever move forward.
  • โœ… Every enter has a matching exit update.
  • โœ… Window length is R - L + 1 (mind the +1).
  • โœ… Output gated correctly (fixed: R >= k-1; variable: after invariant restored).
  • โœ… Edge cases handled: n == 0, k > n, k <= 0, single element, duplicates at boundary.
  • โœ… Aggregate is updated in O(1) โ€” no sum(window) / max(window) inside the loop.
  • โœ… For ML: window is causal (no future leakage) unless intentionally centered.

End of reference guide.


Prerequisites: Arrays & Strings ยท Two Pointers
See also: Two Pointers ยท Hashing Patterns

Section: Core DSA ยท All guides