Variable-Size Window Problems
Variable-size windows expand and shrink based on certain conditions. These are often "longest" or "shortest" subarray/substring problems.
Pattern Recognition
Variable window problems often ask:
- "Longest substring/subarray with..."
- "Shortest substring/subarray with..."
- "At most k..." or "At least k..."
- "Contains all..." or "No repeating..."
The Template
def variable_window_template(arr, condition):
left = 0
window_state = initialize()
result = initialize_result() # max, min, count, etc.
for right in range(len(arr)):
# Expand window - add right element
add_to_window(window_state, arr[right])
# Shrink window while invalid
while not is_valid(window_state, condition):
remove_from_window(window_state, arr[left])
left += 1
# Update result with current valid window
result = update_result(result, right - left + 1)
return result
Problem 1: Longest Substring Without Repeating
Problem: Find length of longest substring without repeating characters.
def length_of_longest_substring(s):
char_set = set()
left = 0
max_len = 0
for right in range(len(s)):
# Shrink until no duplicates
while s[right] in char_set:
char_set.remove(s[left])
left += 1
# Add current char
char_set.add(s[right])
max_len = max(max_len, right - left + 1)
return max_len
print(length_of_longest_substring("abcabcbb")) # 3 ("abc")
print(length_of_longest_substring("pwwkew")) # 3 ("wke")
Time: O(n), Space: O(min(n, alphabet size))
Problem 2: Longest Substring with K Distinct
Problem: Find longest substring with at most k distinct characters.
def longest_substring_k_distinct(s, k):
if k == 0:
return 0
char_freq = {}
left = 0
max_len = 0
for right in range(len(s)):
# Add right char
char = s[right]
char_freq[char] = char_freq.get(char, 0) + 1
# Shrink while too many distinct
while len(char_freq) > k:
left_char = s[left]
char_freq[left_char] -= 1
if char_freq[left_char] == 0:
del char_freq[left_char]
left += 1
max_len = max(max_len, right - left + 1)
return max_len
print(longest_substring_k_distinct("eceba", 2)) # 3 ("ece")
print(longest_substring_k_distinct("aa", 1)) # 2 ("aa")
Problem 3: Minimum Window Substring
Problem: Find minimum window in s containing all characters of t.
from collections import Counter
def min_window(s, t):
if not s or not t or len(s) < len(t):
return ""
required = Counter(t)
needed = len(required)
formed = 0
window_counts = {}
left = 0
min_len = float('inf')
result = ""
for right in range(len(s)):
# Add right char
char = s[right]
window_counts[char] = window_counts.get(char, 0) + 1
# Check if requirement satisfied
if char in required and window_counts[char] == required[char]:
formed += 1
# Try to shrink
while formed == needed and left <= right:
# Update result
if right - left + 1 < min_len:
min_len = right - left + 1
result = s[left:right + 1]
# Remove left char
left_char = s[left]
window_counts[left_char] -= 1
if left_char in required and window_counts[left_char] < required[left_char]:
formed -= 1
left += 1
return result
print(min_window("ADOBECODEBANC", "ABC")) # "BANC"
Key: Expand to make valid, shrink to find minimum.
Problem 4: Max Consecutive Ones III
Problem: Max length of contiguous 1s if you can flip at most k zeros.
def longest_ones(nums, k):
left = 0
zeros = 0
max_len = 0
for right in range(len(nums)):
# Expand
if nums[right] == 0:
zeros += 1
# Shrink if too many zeros
while zeros > k:
if nums[left] == 0:
zeros -= 1
left += 1
max_len = max(max_len, right - left + 1)
return max_len
print(longest_ones([1,1,1,0,0,0,1,1,1,1,0], 2)) # 6
Problem 5: Longest Repeating Character Replacement
Problem: Find longest substring with same letter after replacing at most k characters.
def character_replacement(s, k):
char_freq = {}
left = 0
max_freq = 0
max_len = 0
for right in range(len(s)):
# Add right char
char = s[right]
char_freq[char] = char_freq.get(char, 0) + 1
max_freq = max(max_freq, char_freq[char])
# Window size - max frequency = replacements needed
window_size = right - left + 1
# If too many replacements needed, shrink
if window_size - max_freq > k:
left_char = s[left]
char_freq[left_char] -= 1
left += 1
max_len = max(max_len, right - left + 1)
return max_len
print(character_replacement("ABAB", 2)) # 4 (all same)
print(character_replacement("AABABBA", 1)) # 4 ("AABA" or "ABBA")
Key Insight: window_size - max_frequency = changes needed
Problem 6: Fruits Into Baskets
Problem: Pick maximum fruits with at most 2 types.
def total_fruit(fruits):
"""Longest subarray with at most 2 distinct elements"""
fruit_freq = {}
left = 0
max_fruits = 0
for right in range(len(fruits)):
# Add fruit
fruit = fruits[right]
fruit_freq[fruit] = fruit_freq.get(fruit, 0) + 1
# Shrink if too many types
while len(fruit_freq) > 2:
left_fruit = fruits[left]
fruit_freq[left_fruit] -= 1
if fruit_freq[left_fruit] == 0:
del fruit_freq[left_fruit]
left += 1
max_fruits = max(max_fruits, right - left + 1)
return max_fruits
print(total_fruit([1,2,1])) # 3
print(total_fruit([0,1,2,2])) # 3 ([1,2,2])
print(total_fruit([1,2,3,2,2])) # 4 ([2,3,2,2])
Problem 7: Subarrays with K Different Integers
Problem: Count subarrays with exactly k distinct integers.
def subarrays_with_k_distinct(nums, k):
"""Helper: at most k distinct"""
def at_most_k(k):
freq = {}
left = 0
count = 0
for right in range(len(nums)):
freq[nums[right]] = freq.get(nums[right], 0) + 1
while len(freq) > k:
freq[nums[left]] -= 1
if freq[nums[left]] == 0:
del freq[nums[left]]
left += 1
# All subarrays ending at right
count += right - left + 1
return count
# Exactly k = (at most k) - (at most k-1)
return at_most_k(k) - at_most_k(k - 1)
print(subarrays_with_k_distinct([1,2,1,2,3], 2)) # 7
Key Trick: Exactly k = At most k - At most (k-1)
Problem 8: Minimum Size Subarray Sum
Problem: Find minimum length subarray with sum ≥ target.
def min_subarray_len(target, nums):
left = 0
current_sum = 0
min_len = float('inf')
for right in range(len(nums)):
current_sum += nums[right]
# Shrink while valid
while current_sum >= target:
min_len = min(min_len, right - left + 1)
current_sum -= nums[left]
left += 1
return min_len if min_len != float('inf') else 0
print(min_subarray_len(7, [2,3,1,2,4,3])) # 2 ([4,3])
Tips for Variable Window
- Expand first: Always add right element
- Then shrink: Use while loop to shrink until valid
- Update result: After shrinking, window is valid
- Track window state: Use hash map, set, or counters
- Handle edge cases: Empty array, impossible target