F · Pupil · 14 min

Sliding Window: Expand, Shrink, Payload

A sliding window is two pointers plus a payload. Expand R every step, shrink L while the window is illegal, and maintain the sum or frequency map in O(1) per move so the whole scan stays linear.

Two pointers give you a range [L, R]. A sliding window is that range plus a payload: whatever you need to know about the cells inside it. The payload is usually a sum, a frequency map, a count of distinct values, or a max-deque.

The motion is always the same. R walks the array once. After each include, you shrink `L` while the window is illegal. Each index enters once and leaves once, so the scan is O(n) times the cost of updating the payload — O(1) for a sum, O(1) amortized for a hash map.

Longest subarray with sum ≤ k — positives onlyjs
function maxLength(nums, k) {
  let L = 0;
  let sum = 0;
  let best = 0;
  for (let R = 0; R < nums.length; R++) {
    sum += nums[R];              // expand: include nums[R]
    while (L <= R && sum > k) {  // shrink while illegal
      sum -= nums[L];
      L++;
    }
    best = Math.max(best, R - L + 1);
  }
  return best;
}

TRACE

nums = [2, 1, 3, 4], k = 6. Longest subarray with sum ≤ 6. Watch L, R, and the sum payload.

R = 0, include 2

Window [2]. Payload sum = 2 ≤ 6. Legal. Length 1. best = 1.

L/R2
11
23
34

expand R, sum += 2

1 / 5

The payload is the only thing that changes between window problems. Swap the running sum for a frequency map and you get 'longest substring with at most k distinct characters': increment freq[s[R]] on expand, decrement and maybe delete on shrink, and the illegal test is freq.size > k.

Same skeleton. Different payload. That is the pattern you should recognize in the first twenty seconds.

Same skeleton, frequency payloadjs
function longestAtMostKDistinct(s, k) {
  const freq = new Map();
  let L = 0;
  let best = 0;
  for (let R = 0; R < s.length; R++) {
    freq.set(s[R], (freq.get(s[R]) ?? 0) + 1);
    while (freq.size > k) {
      const c = s[L++];
      const n = freq.get(c) - 1;
      if (n === 0) freq.delete(c);
      else freq.set(c, n);
    }
    best = Math.max(best, R - L + 1);
  }
  return best;
}

CHECK

You want the longest subarray with sum ≤ k. All nums[i] are positive. After nums[R] makes the sum too big, why is it safe to only move L forward (never backward)?

CHECK

Which array makes the two-pointer sum window (expand R, shrink L while sum > k) return a wrong longest length?

CHECK

Longest substring with at most 2 distinct characters. After adding s[R], the map has 3 keys. What is the payload update on the shrink?

Checks 0/3

Next lesson