left/right pointers with expand/shrink logic — expand right to grow the window, shrink left when the window violates the constraint.| Pattern | Window Type | Time | Space | Classic Problems |
|---|---|---|---|---|
| Fixed-size sum/avg | Fixed | O(n) | O(1) | Max sum subarray of size k, moving average |
| Fixed-size with hash | Fixed | O(n) | O(k) | Permutation in string, anagram check |
| Variable shrink (find minimum) | Variable | O(n) | O(k) | Minimum window substring, smallest subarray with sum ≥ target |
| Variable expand (find maximum) | Variable | O(n) | O(k) | Longest substring without repeating chars, longest with at most k distinct |
| Sliding window maximum/minimum | Fixed | O(n) | O(k) | Sliding window maximum (deque-based) |
| Count-based | Variable | O(n) | O(k) | Subarrays with k different integers, count of nice subarrays |
| Flip/toggle | Variable | O(n) | O(1) | Max consecutive ones III (flip at most k zeros) |
| Two-frequency | Variable | O(n) | O(1) | Longest repeating character replacement |
START
├── Is the problem about a contiguous subarray or substring?
│ ├── NO → Not a sliding window problem. Consider two pointers, DP, or binary search.
│ └── YES ↓
├── Is the window size given (fixed k)?
│ ├── YES → Use FIXED-SIZE window template
│ │ ├── Need max/min of window elements? → Use monotonic deque (O(n))
│ │ └── Need sum/count/frequency? → Use running sum + hash map
│ └── NO → Variable-size window ↓
├── Are you looking for the SMALLEST valid window?
│ ├── YES → Use VARIABLE SHRINK template
│ │ └── Expand right until valid, then shrink left to find minimum
│ └── NO ↓
├── Are you looking for the LONGEST valid window?
│ ├── YES → Use VARIABLE EXPAND template
│ │ └── Expand right, shrink left only when invalid, track max length
│ └── NO ↓
└── Counting valid subarrays?
└── YES → Use "at most K" trick: count(exactly K) = count(at most K) - count(at most K-1)
Determine whether the problem specifies a fixed window size k or requires you to find an optimal (minimum/maximum) window. If the problem says "subarray of size k" or "every k consecutive elements," use the fixed template. If it says "smallest subarray such that..." or "longest substring where...," use the variable template. [src1]
Set up tracking variables: left = 0, running sum/counter/hash map, and result variable. For hash map problems, use collections.Counter (Python), Map (JavaScript), or HashMap (Java). [src3]
left = 0
window_state = {} # or: current_sum = 0
result = float('inf') # for minimum; use float('-inf') or 0 for maximum
Iterate right from 0 to len(arr)-1. On each step, add arr[right] to the window state. For sum problems: current_sum += arr[right]. For frequency problems: window_state[arr[right]] += 1. [src2]
For fixed windows: shrink when right - left + 1 > k. For variable windows: shrink while the window is invalid (shrink template) or while the window violates the constraint (expand template). On each shrink, remove arr[left] from state and increment left. [src3]
After adjusting the window, check if the current window is a candidate for the answer. For minimum problems, update when the window becomes valid. For maximum problems, update on every step. [src4]
Verify: Run through a small example manually — the left pointer should never exceed right, and both should reach the end exactly once, confirming O(n).
# Input: arr = [2, 1, 5, 1, 3, 2], k = 3
# Output: 9 (subarray [5, 1, 3])
def max_sum_subarray(arr, k):
n = len(arr)
if n < k:
return -1
window_sum = sum(arr[:k]) # first window
max_sum = window_sum
for right in range(k, n):
window_sum += arr[right] - arr[right - k] # slide: add new, remove old
max_sum = max(max_sum, window_sum)
return max_sum
# Input: s = "ADOBECODEBANC", t = "ABC"
# Output: "BANC"
from collections import Counter
def min_window(s, t):
need = Counter(t)
missing = len(t) # chars still needed
left = 0
best = (0, float('inf')) # (start, end)
for right, char in enumerate(s):
if need[char] > 0:
missing -= 1
need[char] -= 1
while missing == 0: # window is valid — try to shrink
if right - left < best[1] - best[0]:
best = (left, right)
need[s[left]] += 1
if need[s[left]] > 0:
missing += 1
left += 1
return "" if best[1] == float('inf') else s[best[0]:best[1] + 1]
// Input: s = "abcabcbb"
// Output: 3 (substring "abc")
function lengthOfLongestSubstring(s) {
const seen = new Map(); // char -> last index
let left = 0;
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
if (seen.has(s[right]) && seen.get(s[right]) >= left) {
left = seen.get(s[right]) + 1; // shrink past duplicate
}
seen.set(s[right], right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
// Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
// Output: 6 (flip two 0s to get [1,1,1,0,0,1,1,1,1,1,1])
public int longestOnes(int[] nums, int k) {
int left = 0, zeros = 0, maxLen = 0;
for (int right = 0; right < nums.length; right++) {
if (nums[right] == 0) zeros++;
while (zeros > k) { // too many flips — shrink
if (nums[left] == 0) zeros--;
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
# BAD — O(n^2) brute force for max sum subarray of size k
def max_sum_brute(arr, k):
max_sum = 0
for i in range(len(arr) - k + 1):
current = sum(arr[i:i+k]) # recalculates entire window each time
max_sum = max(max_sum, current)
return max_sum
# GOOD — O(n) sliding window
def max_sum_slide(arr, k):
window_sum = sum(arr[:k])
max_sum = window_sum
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k] # O(1) update
max_sum = max(max_sum, window_sum)
return max_sum
# BAD — shrinks left pointer but forgets to remove element from hash map
def longest_unique_bad(s):
seen = set()
left = 0
max_len = 0
for right in range(len(s)):
seen.add(s[right])
while s[right] in seen and len(seen) > right - left + 1:
left += 1 # moves left but never removes s[left] from seen!
max_len = max(max_len, right - left + 1)
return max_len
# GOOD — properly maintains the set
def longest_unique_good(s):
seen = set()
left = 0
max_len = 0
for right in range(len(s)):
while s[right] in seen:
seen.remove(s[left]) # remove before moving left
left += 1
seen.add(s[right])
max_len = max(max_len, right - left + 1)
return max_len
# BAD — resets left to 0, destroying O(n) guarantee
def min_subarray_bad(arr, target):
for right in range(len(arr)):
left = 0 # WRONG: restarts from beginning every time
while left <= right and sum(arr[left:right+1]) >= target:
left += 1
# This is O(n^2) despite looking like sliding window
# GOOD — left only advances, never resets
def min_subarray_good(arr, target):
left = 0
current_sum = 0
min_len = float('inf')
for right in range(len(arr)):
current_sum += arr[right]
while current_sum >= target:
min_len = min(min_len, right - left + 1)
current_sum -= arr[left]
left += 1 # only moves forward
return min_len if min_len != float('inf') else 0
right - left + 1, not right - left. Using the wrong formula gives windows of size k-1. Fix: always use right - left + 1 for inclusive bounds. [src1]len(arr) < k or len(s) == 0, the function should return 0 or -1 immediately. Fix: add a guard clause at the start. [src5]while (shrink as much as possible). Mixing them up gives wrong results. Fix: use while for shrink-to-minimum, while for shrink-when-invalid. [src3]matched counter. [src2]need[char] > 0 after incrementing. [src3]| Use When | Don't Use When | Use Instead |
|---|---|---|
| Finding max/min/count of contiguous subarray or substring | Elements are not contiguous (subsequence problems) | Dynamic programming |
| Problem says "subarray of size k" or "window of length k" | Need to find pairs in sorted array (two-sum variant) | Two-pointer (converging) |
| Optimizing longest/shortest subarray meeting a condition | Array has negative numbers and need exact subarray sum | Prefix sum + hash map |
| Condition is monotonic (expanding only worsens/improves) | Comparing non-adjacent elements | Binary search or divide & conquer |
| String pattern matching within a window (anagrams, permutations) | Longest increasing subsequence (non-contiguous) | Patience sorting / DP |
| Counting subarrays satisfying a property | Data structure is a tree or graph | BFS/DFS traversal |
exactly(K) = atMost(K) - atMost(K-1) with two sliding window passescurrent_sum ≥ target does not imply current_sum + next ≥ target, so the shrink step may skip valid windows