Skip to content

Latest commit

Β 

History

History
552 lines (432 loc) Β· 20.1 KB

File metadata and controls

552 lines (432 loc) Β· 20.1 KB

Sliding Window β€” Notes & Templates

Pattern family: Array / String Optimization Difficulty range: Easy β†’ Hard Language: Java Problems file: 02_sliding_window_pattern_problems.md


Table of Contents

  1. How to identify this pattern ← start here
  2. What is this pattern?
  3. Core rules
  4. 2-Question framework
  5. Variants table
  6. Universal Java template
  7. Quick reference cheatsheet
  8. Common mistakes
  9. Complexity summary

1. How to identify this pattern

Keyword scanner

If you see this... It means
"subarray / substring of size k" fixed window
"maximum / minimum sum of subarray of size k" fixed window
"longest substring with at most k distinct" variable window (maximize)
"smallest subarray with sum β‰₯ target" variable window (minimize)
"no-repeat / without repeating characters" variable window with HashSet/Map
"permutation / anagram of pattern in string" fixed window (pattern length)
"minimum window containing all characters" variable window (minimize)
"replace at most k characters" variable window with frequency map
"contiguous subarray / substring" sliding window candidate
"subarray product less than k" variable window

Brute force test

Brute force: check every subarray β€” O(nΒ²) or O(nΒ³)
β†’ If you need max/min/count of a contiguous subarray/substring property
  AND that property is monotone (adding element doesn't decrease window validity)
β†’ Sliding Window = O(n)

Key insight: instead of recomputing from scratch each time,
SLIDE the window β€” add one element on right, remove one on left.

The 5 confirming signals

Signal 1 β€” "Contiguous subarray or substring"

Non-contiguous = DP or greedy. Contiguous + optimize = Sliding Window.

Signal 2 β€” Fixed size k given

Window never changes size β†’ Fixed window. Slide one step at a time.

Signal 3 β€” "Longest / maximum" with a constraint

Expand right always, shrink left when constraint violated β†’ Variable window maximize.

Signal 4 β€” "Shortest / minimum" with a constraint

Expand right until condition met, then shrink left as much as possible β†’ Variable window minimize.

Signal 5 β€” "At most k" convertible to exact count

exactly(k) = atMost(k) - atMost(k-1) β†’ use variable window twice.

Decision flowchart

Problem asks about contiguous subarray/substring?
        β”‚
        β”œβ”€β”€ YES β†’ Is window size fixed (given as k)?
        β”‚              β”œβ”€β”€ YES β†’ Fixed Window
        β”‚              └── NO  β†’ Is it maximize or minimize?
        β”‚                             β”œβ”€β”€ MAXIMIZE β†’ Variable Window (expand right, shrink on violation)
        β”‚                             └── MINIMIZE β†’ Variable Window (expand until met, shrink as much as possible)
        β”‚                             └── COUNT EXACT k β†’ At Most trick: atMost(k) - atMost(k-1)
        β”‚
        └── NO β†’ Not Sliding Window (try DP / Two Pointers / Binary Search)

2. What is this pattern?

A window is a contiguous portion of an array or string defined by two pointers β€” left and right. Instead of recomputing the entire window from scratch each step, you slide it:

Expand:  right++  (add new element to window)
Shrink:  left++   (remove oldest element from window)

This converts O(nΒ²) brute force into O(n) by reusing computation.

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

Window [2,1,5] β†’ sum=8
Slide  [1,5,2] β†’ sum = 8 - 2 + 2 = 8  (don't recompute from scratch)
Slide  [5,2,3] β†’ sum = 8 - 1 + 3 = 10
Slide  [2,3,2] β†’ sum = 10 - 5 + 2 = 7

Two fundamentally different window types:

Type Window size Right pointer Left pointer Goal
Fixed always = k moves every step moves every step (right - k) aggregate over all windows of size k
Variable changes always moves right moves when condition violated (maximize) OR when condition met (minimize) find optimal window

3. Core rules

Rule 1 β€” Fixed window: add right, remove left simultaneously

// Window size always = k
for (int right = 0; right < n; right++) {
    // add arr[right] to window
    windowSum += arr[right];

    if (right >= k - 1) {
        // window is full β€” use result
        maxSum = Math.max(maxSum, windowSum);
        // remove leftmost element
        windowSum -= arr[right - k + 1];   // left = right - k + 1
    }
}

Rule 2 β€” Variable window maximize: shrink until valid, never discard answer

int left = 0;
for (int right = 0; right < n; right++) {
    // expand β€” add arr[right]

    while (/* window invalid */) {
        // shrink β€” remove arr[left]
        left++;
    }
    // window is now valid β€” update answer
    maxLen = Math.max(maxLen, right - left + 1);
}

Rule 3 β€” Variable window minimize: shrink as long as valid, update answer inside shrink

int left = 0;
for (int right = 0; right < n; right++) {
    // expand β€” add arr[right]

    while (/* window still valid β€” can shrink more */) {
        // update answer BEFORE shrinking
        minLen = Math.min(minLen, right - left + 1);
        // shrink β€” remove arr[left]
        left++;
    }
}

Key difference:

  • Maximize β†’ update answer after the while loop (window just became valid)
  • Minimize β†’ update answer inside the while loop (window is currently valid)

4. 2-Question framework

Question 1 β€” Is the window size fixed or variable?

Answer Type Clue words
Size k given explicitly Fixed Window "subarray of size k", "window of length k"
Size changes based on condition Variable Window "longest", "shortest", "at most k", "minimum window"

Question 2 β€” What are you optimizing? (variable window only)

Goal Approach Update answer
Maximize window (longest/max) shrink when invalid after while loop
Minimize window (shortest/min) shrink when valid inside while loop
Count windows with exact k atMost(k) - atMost(k-1) count = right - left + 1 per step

Decision shortcut:

  • "fixed size k" β†’ fixed window
  • "longest / maximum" + constraint β†’ variable maximize
  • "shortest / minimum" + condition β†’ variable minimize
  • "count subarrays with exactly k" β†’ at-most trick
  • "permutation / anagram of pattern" β†’ fixed window = pattern.length()

5. Variants table

Common core: left and right pointers, expand right, conditionally shrink left
What differs: when to shrink, what to track, when to update answer

Variant Track in window Shrink condition Answer update
Fixed window β€” max/min sum running sum always when right >= k-1 each full window
Fixed window β€” pattern match char frequency map always when right >= k-1 when map matches
Variable β€” maximize length char/int frequency when constraint violated after shrink
Variable β€” minimize length running sum when condition still holds inside shrink loop
Variable β€” at most k distinct distinct count when distinct > k right - left + 1 per step
Variable β€” at most k replacements max freq in window when (window - maxFreq) > k after shrink
At-most to exact β€” atMost(k) - atMost(k-1) accumulated count

5b. Pattern categories β€” all types covered

Category 1 β€” Fixed Window

Window size = k throughout. Slide one step at a time.

Pattern:

for (int right = 0; right < n; right++) {
    add(arr[right]);
    if (right >= k - 1) {
        updateAnswer();
        remove(arr[right - k + 1]);
    }
}

Problems: Max Sum Subarray Size K, Find All Anagrams, Permutation in String, Max Consecutive Ones (fixed)


Category 2 β€” Variable Window: Maximize

Expand right always. Shrink left only when window becomes invalid.
Answer = max window size seen while valid.

Pattern:

int left = 0;
for (int right = 0; right < n; right++) {
    add(arr[right]);
    while (isInvalid()) {
        remove(arr[left++]);
    }
    maxLen = Math.max(maxLen, right - left + 1);  // update AFTER shrink
}

Sub-types:

  • 2a. Maximize with distinct count limit β€” use HashMap<char, count>, shrink when map.size() > k
  • 2b. Maximize with replacement limit β€” track maxFreq, shrink when (window - maxFreq) > k
  • 2c. Maximize with no-repeat constraint β€” use HashSet or HashMap<char, lastIndex>

Problems: Longest Substring K Distinct, Fruits into Baskets, No-repeat Substring, Longest After Replacement, Max Consecutive Ones III


Category 3 β€” Variable Window: Minimize

Expand right until condition met. Then shrink left as much as possible.
Answer = min window size seen while valid.

Pattern:

int left = 0;
for (int right = 0; right < n; right++) {
    add(arr[right]);
    while (isValid()) {
        minLen = Math.min(minLen, right - left + 1);  // update INSIDE shrink
        remove(arr[left++]);
    }
}

Problems: Smallest Subarray with Given Sum, Minimum Window Substring, Min Size Subarray Sum


Category 4 β€” At-Most to Exact (Count problems)

"Exactly k" is hard directly. Convert using:
count(exactly k) = count(at most k) - count(at most k-1)

Pattern:

return atMost(nums, k) - atMost(nums, k - 1);

private int atMost(int[] nums, int k) {
    int left = 0, count = 0;
    for (int right = 0; right < nums.length; right++) {
        // add nums[right]
        while (/* invalid: exceeded k */) left++;
        count += right - left + 1;  // all windows ending at right
    }
    return count;
}

Problems: Subarrays with K Different Integers, Binary Subarrays with Sum, Count Nice Subarrays


Summary β€” all pattern types at a glance

# Type Window size Shrink when Update answer
1 Fixed window always k every step each full window
2a Variable maximize (distinct) grows/shrinks distinct > k after shrink
2b Variable maximize (replace) grows/shrinks replacements > k after shrink
2c Variable maximize (no-repeat) grows/shrinks duplicate found after shrink
3 Variable minimize grows/shrinks condition still holds inside shrink
4 At-most to exact variable β€” atMost(k) - atMost(k-1)

6. Universal Java template

// ─── TEMPLATE A: Fixed Window ─────────────────────────────────────────────────
public int fixedWindow(int[] arr, int k) {
    int windowSum = 0, maxSum = 0;

    for (int right = 0; right < arr.length; right++) {
        windowSum += arr[right];                        // expand

        if (right >= k - 1) {
            maxSum = Math.max(maxSum, windowSum);       // update answer
            windowSum -= arr[right - k + 1];            // shrink: remove leftmost
        }
    }
    return maxSum;
}

// ─── TEMPLATE B: Variable Window β€” Maximize ───────────────────────────────────
// "Longest substring / subarray satisfying condition"
public int variableMaximize(String s, int k) {
    Map<Character, Integer> freq = new HashMap<>();
    int left = 0, maxLen = 0;

    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        freq.put(c, freq.getOrDefault(c, 0) + 1);      // expand

        while (isInvalid(freq, k)) {                    // shrink until valid
            char lc = s.charAt(left);
            freq.put(lc, freq.get(lc) - 1);
            if (freq.get(lc) == 0) freq.remove(lc);
            left++;
        }
        maxLen = Math.max(maxLen, right - left + 1);    // update AFTER shrink
    }
    return maxLen;
}

// ─── TEMPLATE C: Variable Window β€” Minimize ───────────────────────────────────
// "Smallest subarray / substring satisfying condition"
public int variableMinimize(int[] arr, int target) {
    int windowSum = 0, left = 0;
    int minLen = Integer.MAX_VALUE;

    for (int right = 0; right < arr.length; right++) {
        windowSum += arr[right];                        // expand

        while (windowSum >= target) {                   // condition MET β†’ shrink
            minLen = Math.min(minLen, right - left + 1); // update INSIDE shrink
            windowSum -= arr[left++];
        }
    }
    return minLen == Integer.MAX_VALUE ? 0 : minLen;
}

// ─── TEMPLATE D: At-Most to Exact (Count exact k) ────────────────────────────
public int countExact(int[] nums, int k) {
    return atMost(nums, k) - atMost(nums, k - 1);
}

private int atMost(int[] nums, int k) {
    Map<Integer, Integer> freq = new HashMap<>();
    int left = 0, count = 0;

    for (int right = 0; right < nums.length; right++) {
        freq.put(nums[right], freq.getOrDefault(nums[right], 0) + 1);

        while (freq.size() > k) {                      // shrink until valid
            int lv = nums[left++];
            freq.put(lv, freq.get(lv) - 1);
            if (freq.get(lv) == 0) freq.remove(lv);
        }
        count += right - left + 1;                     // all valid windows ending at right
    }
    return count;
}

// ─── TEMPLATE E: Fixed Window β€” Pattern Match (Anagram / Permutation) ─────────
public boolean patternMatch(String s, String p) {
    if (p.length() > s.length()) return false;
    int[] pFreq = new int[26], wFreq = new int[26];

    for (char c : p.toCharArray()) pFreq[c - 'a']++;

    for (int right = 0; right < s.length(); right++) {
        wFreq[s.charAt(right) - 'a']++;                // expand

        if (right >= p.length()) {                      // shrink: remove leftmost
            wFreq[s.charAt(right - p.length()) - 'a']--;
        }
        if (Arrays.equals(pFreq, wFreq)) return true;  // match check
    }
    return false;
}

7. Quick reference cheatsheet

SIGNAL IN PROBLEM                              β†’ TYPE                   β†’ KEY OPERATION
──────────────────────────────────────────────────────────────────────────────────────────
"max/min sum of subarray of size k"            β†’ Fixed window           β†’ add right, remove arr[right-k+1]
"permutation / anagram of pattern in s"        β†’ Fixed window           β†’ int[26] freq compare
"longest substring with at most k distinct"    β†’ Variable maximize      β†’ map.size() > k β†’ shrink
"longest with at most k replacements"          β†’ Variable maximize      β†’ (window-maxFreq) > k β†’ shrink
"longest without repeating characters"         β†’ Variable maximize      β†’ duplicate in map β†’ shrink
"smallest subarray with sum β‰₯ target"          β†’ Variable minimize      β†’ sum >= target β†’ shrink inside
"minimum window containing all chars"          β†’ Variable minimize      β†’ formed==required β†’ shrink inside
"count subarrays with exactly k distinct"      β†’ At-most trick          β†’ atMost(k) - atMost(k-1)
"subarray product less than k"                 β†’ Variable minimize      β†’ product >= k β†’ shrink
"max consecutive ones with k flips"            β†’ Variable maximize      β†’ zeros > k β†’ shrink

Template picker β€” one question decides:

Fixed size k given?          β†’ Template A (Fixed Window)
Maximize + constraint?       β†’ Template B (Variable Maximize)
Minimize + condition to meet?β†’ Template C (Variable Minimize)
Count exact k subarrays?     β†’ Template D (At-Most Trick)
Anagram / permutation match? β†’ Template E (Pattern Match)

The one insight that unlocks all variable window problems:

Maximize β†’ shrink when INVALID  β†’ answer AFTER while
Minimize β†’ shrink when VALID    β†’ answer INSIDE while

8. Common mistakes

Mistake 1 β€” Updating answer at wrong place (maximize vs minimize)

// BUG (maximize): updating inside shrink loop β†’ counts invalid windows
while (invalid()) { left++; }
maxLen = Math.max(maxLen, right - left + 1);  // WRONG β€” should be after

// FIX (maximize): update AFTER shrink
while (invalid()) { remove(arr[left]); left++; }
maxLen = Math.max(maxLen, right - left + 1);  // CORRECT

// BUG (minimize): updating after shrink β†’ misses valid windows
while (valid()) { left++; }
minLen = Math.min(minLen, right - left + 1);  // WRONG β€” already invalid

// FIX (minimize): update INSIDE shrink
while (valid()) {
    minLen = Math.min(minLen, right - left + 1);  // CORRECT
    remove(arr[left]); left++;
}

Mistake 2 β€” Not handling "no valid window" for minimize

// BUG β€” returns Integer.MAX_VALUE if no valid window exists
return minLen;

// FIX
return minLen == Integer.MAX_VALUE ? 0 : minLen;

Mistake 3 β€” Using size() instead of value for distinct count

// BUG β€” map.size() counts distinct keys, but you may want total count
while (map.size() > k) { ... }   // correct for "at most k distinct"

// But for replacement problems, track maxFreq separately:
maxFreq = Math.max(maxFreq, freq[c - 'a']);
while ((right - left + 1) - maxFreq > k) { ... }
// Note: maxFreq may be stale but safe β€” it only grows, never causes over-shrinking

Mistake 4 β€” maxFreq not updated correctly in replacement problems

// BUG β€” decreasing maxFreq when shrinking (not needed, causes wrong answers)
while ((right - left + 1) - maxFreq > k) {
    freq[s.charAt(left) - 'a']--;
    maxFreq--;   // WRONG β€” maxFreq is a historical max, don't decrease
    left++;
}

// FIX β€” only increase maxFreq when expanding, never decrease
maxFreq = Math.max(maxFreq, freq[s.charAt(right) - 'a']);
while ((right - left + 1) - maxFreq > k) {
    freq[s.charAt(left) - 'a']--;
    left++;   // maxFreq stays β€” safe because window can only shrink to valid size
}

Mistake 5 β€” Off-by-one in fixed window removal

// BUG β€” removing wrong element
windowSum -= arr[left];   // left not updated yet β†’ wrong index

// FIX β€” remove element that just left the window
windowSum -= arr[right - k + 1];   // or: windowSum -= arr[left]; left++;

Mistake 6 β€” Forgetting to shrink map entry before removing key

// BUG β€” removing key without decrementing first
map.remove(arr[left]);   // count lost

// FIX β€” decrement first, then remove if zero
map.put(arr[left], map.get(arr[left]) - 1);
if (map.get(arr[left]) == 0) map.remove(arr[left]);
left++;

9. Complexity summary

Variant Time Space Notes
Fixed window β€” sum O(n) O(1) running sum only
Fixed window β€” pattern match (int[26]) O(n) O(1) fixed 26-char array
Variable maximize β€” distinct count O(n) O(k) HashMap of at most k distinct
Variable maximize β€” replacement O(n) O(1) int[26] array
Variable maximize β€” no-repeat O(n) O(min(n,Ξ±)) Ξ± = alphabet size
Variable minimize β€” sum O(n) O(1)
Variable minimize β€” min window substring O(n + m) O(m) m = pattern length
At-most to exact O(n) O(k) two passes of variable window

General rules:

  • All sliding window β†’ O(n) time β€” each element enters and leaves the window at most once
  • Space is O(1) for integer arrays, O(k) or O(alphabet) for HashMap-based windows
  • Never O(nΒ²) β€” if your solution has nested loops over different ranges, it is not sliding window