Sliding Window Pattern

The Sliding Window technique is used to find a subarray or substring that satisfies a specific condition. Instead of recalculating everything for each window, we efficiently update the window by adding one element and removing another.

When to Use This Pattern

  • Problems involving contiguous subarrays or substrings
  • Finding maximum/minimum sum of subarray of fixed size
  • Finding longest/shortest subarray with certain properties
  • Problems with "substring" or "subarray" keywords
  • When you need to maintain a window of elements

Key Concepts

There are two main types of sliding windows:

Visual Representation

Sliding Window - Fixed Size (k=3)

Array: [2, 1, 5, 1, 3, 2] | Window Size: k = 3 2 [0] 1 [1] 5 [2] 1 [3] 3 [4] 2 [5] Window 1 sum = 2 + 1 + 5 = 8 Slide → 1 [1] 5 [2] 1 [3] Window 2 Remove: -2 Add: +1 sum = 8 - 2 + 1 = 7 Key Insight: Efficient Window Update Instead of recalculating sum from scratch (O(k)) We update: remove leftmost + add rightmost (O(1)) This reduces time complexity from O(nk) to O(n)

Efficiency: Each window update is O(1) instead of O(k), making the overall algorithm O(n) instead of O(nk)

1. Fixed Size Window

The window size remains constant. We slide it by removing one element from the left and adding one to the right.

window_sum = sum of first k elements
max_sum = window_sum

for i from k to n-1:
    window_sum = window_sum - arr[i-k] + arr[i]
    max_sum = max(max_sum, window_sum)

2. Variable Size Window

The window size changes based on conditions. We expand the window when we need more elements and shrink it when we have enough.

left = 0
for right from 0 to n-1:
    # Expand window
    add arr[right] to window
    
    # Shrink window while condition is met
    while condition is satisfied:
        # Process current window
        update result
        
        # Remove left element
        remove arr[left] from window
        left += 1

Visual Simulation: Fixed Size Window

Finding maximum sum of subarray of size k = 3 in [2, 1, 5, 1, 3, 2]:

Array: [2, 1, 5, 1, 3, 2], k = 3

Window 1 (indices 0-2):

Elements: [2, 1, 5], sum = 8

max_sum = 8

Window 2 (indices 1-3):

Remove 2, add 1: sum = 8 - 2 + 1 = 7

Elements: [1, 5, 1], max_sum = max(8, 7) = 8

Window 3 (indices 2-4):

Remove 1, add 3: sum = 7 - 1 + 3 = 9

Elements: [5, 1, 3], max_sum = max(8, 9) = 9

Window 4 (indices 3-5):

Remove 5, add 2: sum = 9 - 5 + 2 = 6

Elements: [1, 3, 2], max_sum = max(9, 6) = 9

Result: 9

Algorithm Section

Fixed Size Window Algorithm

function maxSumFixedWindow(arr, k):
    n = length(arr)
    if n < k:
        return -1
    
    window_sum = sum of first k elements
    max_sum = window_sum
    
    for i from k to n-1:
        window_sum = window_sum - arr[i-k] + arr[i]
        max_sum = max(max_sum, window_sum)
    
    return max_sum

Variable Size Window Algorithm

function longestSubarrayWithCondition(arr, condition):
    left = 0
    max_length = 0
    window_state = initialize()
    
    for right from 0 to n-1:
        window_state.add(arr[right])
        
        while not condition(window_state):
            window_state.remove(arr[left])
            left += 1
        
        max_length = max(max_length, right - left + 1)
    
    return max_length

Implementation in C++

#include <vector>
#include <algorithm>
#include <unordered_map>
#include <string>
using namespace std;

// Fixed Size Window: Maximum sum of subarray of size k
int maxSumFixedWindow(vector<int>& nums, int k) {
    int n = nums.size();
    if (n < k) {
        return -1;
    }
    
    // Calculate sum of first window
    int window_sum = 0;
    for (int i = 0; i < k; i++) {
        window_sum += nums[i];
    }
    
    int max_sum = window_sum;
    
    // Slide the window
    for (int i = k; i < n; i++) {
        // Remove leftmost element, add rightmost element
        window_sum = window_sum - nums[i - k] + nums[i];
        max_sum = max(max_sum, window_sum);
    }
    
    return max_sum;
}

// Variable Size Window: Longest substring without repeating characters
int longestSubstringWithoutRepeating(string s) {
    unordered_map<char, int> char_map;
    int left = 0;
    int max_length = 0;
    
    for (int right = 0; right < s.length(); right++) {
        // If character seen and within current window, move left pointer
        if (char_map.find(s[right]) != char_map.end() && 
            char_map[s[right]] >= left) {
            left = char_map[s[right]] + 1;
        }
        
        // Update character's last position
        char_map[s[right]] = right;
        
        // Update maximum length
        max_length = max(max_length, right - left + 1);
    }
    
    return max_length;
}

Detailed Explanation

  • Fixed Window: We calculate the sum of the first k elements, then slide the window by subtracting the leftmost element and adding the new rightmost element. This avoids recalculating the entire sum each time.
  • Variable Window: We expand the window by moving the right pointer. When we encounter a duplicate character within our current window, we shrink the window by moving the left pointer to just after the last occurrence of that character.
  • Time Complexity: O(n) for both - each element is visited at most twice
  • Space Complexity: O(1) for fixed window, O(min(n, m)) for variable window where m is character set size

Practice Problems

Problem 1: Maximum Average Subarray I (LeetCode #643)

You are given an integer array nums consisting of n elements, and an integer k. Find a contiguous subarray whose length is equal to k that has the maximum average value and return this value.

Example:

Input: nums = [1,12,-5,-6,50,3], k = 4
Output: 12.75000
Explanation: Maximum average is (12-5-6+50)/4 = 51/4 = 12.75

Solution in C++:

#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

double findMaxAverage(vector<int>& nums, int k) {
    int n = nums.size();
    
    // Calculate sum of first window
    int window_sum = 0;
    for (int i = 0; i < k; i++) {
        window_sum += nums[i];
    }
    
    int max_sum = window_sum;
    
    // Slide the window
    for (int i = k; i < n; i++) {
        // Remove leftmost, add rightmost
        window_sum = window_sum - nums[i - k] + nums[i];
        max_sum = max(max_sum, window_sum);
    }
    
    return (double)max_sum / k;
}

Step-by-Step Simulation:

Input: nums = [1,12,-5,-6,50,3], k = 4

Window 1 (indices 0-3):

window_sum = 1+12+(-5)+(-6) = 2, max_sum = 2

Window 2 (indices 1-4):

window_sum = 2 - 1 + 50 = 51, max_sum = max(2, 51) = 51

Window 3 (indices 2-5):

window_sum = 51 - 12 + 3 = 42, max_sum = max(51, 42) = 51

Result: 51/4 = 12.75

Explanation: This is a classic fixed-size sliding window problem. We calculate the sum of the first k elements, then slide the window by subtracting the leftmost element and adding the new rightmost element. We track the maximum sum and return the average. This avoids recalculating the entire sum for each window, making it O(n) instead of O(nk).

Time Complexity: O(n)

Space Complexity: O(1)

Problem 2: Longest Substring Without Repeating Characters (LeetCode #3)

Given a string s, find the length of the longest substring without repeating characters.

Example:

Input: s = "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.

Solution in C++:

#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;

int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> char_map;
    int left = 0;
    int max_length = 0;
    
    for (int right = 0; right < s.length(); right++) {
        // If character seen and within current window, move left pointer
        if (char_map.find(s[right]) != char_map.end() && 
            char_map[s[right]] >= left) {
            left = char_map[s[right]] + 1;
        }
        
        // Update character's last position
        char_map[s[right]] = right;
        
        // Update maximum length
        max_length = max(max_length, right - left + 1);
    }
    
    return max_length;
}

Step-by-Step Simulation:

Input: s = "abcabcbb"

right=0 ('a'): left=0, window="a", max_length=1

right=1 ('b'): left=0, window="ab", max_length=2

right=2 ('c'): left=0, window="abc", max_length=3

right=3 ('a'): 'a' found at 0, left=1, window="bca", max_length=3

right=4 ('b'): 'b' found at 1, left=2, window="cab", max_length=3

right=5 ('c'): 'c' found at 2, left=3, window="abc", max_length=3

right=6 ('b'): 'b' found at 4, left=5, window="cb", max_length=3

right=7 ('b'): 'b' found at 6, left=7, window="b", max_length=3

Result: 3

Explanation: We use a variable-size sliding window with a hash map to track the last occurrence of each character. When we encounter a duplicate character within our current window, we move the left pointer to just after the last occurrence. This ensures our window always contains unique characters. The key insight is that we only need to track the last occurrence of each character, not all occurrences.

Time Complexity: O(n)

Space Complexity: O(min(n, m)) where m is the character set size

Problem 3: Minimum Window Substring (LeetCode #76)

Given two strings s and t, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string "".

Example:

Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.

Solution in C++:

#include <string>
#include <unordered_map>
#include <climits>
using namespace std;

string minWindow(string s, string t) {
    if (s.empty() || t.empty()) {
        return "";
    }
    
    // Count characters needed from t
    unordered_map<char, int> dict_t;
    for (char c : t) {
        dict_t[c]++;
    }
    
    int required = dict_t.size();
    int formed = 0;
    
    // Current window character counts
    unordered_map<char, int> window_counts;
    int left = 0, right = 0;
    int min_len = INT_MAX;
    int min_left = 0;
    
    while (right < s.length()) {
        char c = s[right];
        window_counts[c]++;
        
        // Check if current character's frequency matches requirement
        if (dict_t.find(c) != dict_t.end() && 
            window_counts[c] == dict_t[c]) {
            formed++;
        }
        
        // Try to shrink window from left
        while (left <= right && formed == required) {
            // Update minimum window
            if (right - left + 1 < min_len) {
                min_len = right - left + 1;
                min_left = left;
            }
            
            // Remove leftmost character
            char left_char = s[left];
            window_counts[left_char]--;
            if (dict_t.find(left_char) != dict_t.end() && 
                window_counts[left_char] < dict_t[left_char]) {
                formed--;
            }
            
            left++;
        }
        
        right++;
    }
    
    return (min_len == INT_MAX) ? "" : s.substr(min_left, min_len);
}

Explanation: We use a variable-size sliding window. We expand the window by moving the right pointer and track character frequencies. When all required characters are present (formed == required), we try to shrink the window from the left to find the minimum valid window. We track the minimum length and starting position. The 'formed' variable counts how many unique characters have reached their required frequency, allowing us to know when the window is valid.

Time Complexity: O(|s| + |t|)

Space Complexity: O(|s| + |t|)

Problem 4: Maximum Sum Subarray of Size K

Given an array of integers and a number k, find the maximum sum of a subarray of size k.

Example:

Input: arr = [2, 1, 5, 1, 3, 2], k = 3
Output: 9
Explanation: Subarray [5, 1, 3] has maximum sum 9.

Solution in C++:

#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int maxSumSubarray(vector<int>& arr, int k) {
    int n = arr.size();
    if (n < k) {
        return -1;
    }
    
    // Calculate sum of first window
    int window_sum = 0;
    for (int i = 0; i < k; i++) {
        window_sum += arr[i];
    }
    
    int max_sum = window_sum;
    
    // Slide the window
    for (int i = k; i < n; i++) {
        window_sum = window_sum - arr[i - k] + arr[i];
        max_sum = max(max_sum, window_sum);
    }
    
    return max_sum;
}

Explanation: This is a straightforward fixed-size sliding window problem. We maintain a window of size k, calculate its sum, and slide it through the array. At each step, we update the maximum sum found so far. By reusing the previous window's sum and only adjusting for the elements entering and leaving, we achieve O(n) time complexity instead of O(nk).

Time Complexity: O(n)

Space Complexity: O(1)

Problem 5: Longest Repeating Character Replacement (LeetCode #424)

You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English letter. You can perform this operation at most k times. Return the length of the longest substring containing the same letter you can get after performing the above operations.

Example:

Input: s = "AABABBA", k = 1
Output: 4
Explanation: Replace the one 'A' in the middle with 'B' and form "AABBBBA".

Solution in C++:

#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;

int characterReplacement(string s, int k) {
    unordered_map<char, int> char_count;
    int left = 0;
    int max_count = 0;  // Maximum frequency in current window
    int max_length = 0;
    
    for (int right = 0; right < s.length(); right++) {
        char_count[s[right]]++;
        max_count = max(max_count, char_count[s[right]]);
        
        // If window needs more than k replacements, shrink it
        // (window_size - max_frequency) = number of characters to replace
        if ((right - left + 1) - max_count > k) {
            char_count[s[left]]--;
            left++;
        }
        
        max_length = max(max_length, right - left + 1);
    }
    
    return max_length;
}

Step-by-Step Simulation:

Input: s = "AABABBA", k = 1

right=0 ('A'): count[A]=1, max_count=1, window="A", length=1

right=1 ('A'): count[A]=2, max_count=2, window="AA", length=2

right=2 ('B'): count[B]=1, max_count=2, window="AAB", (3-2=1 <= 1), length=3

right=3 ('A'): count[A]=3, max_count=3, window="AABA", (4-3=1 <= 1), length=4

right=4 ('B'): count[B]=2, max_count=3, window="AABAB", (5-3=2 > 1), shrink

left=1, window="ABAB", length=4

right=5 ('B'): count[B]=3, max_count=3, window="ABABB", (5-3=2 > 1), shrink

left=2, window="ABB", length=3

right=6 ('A'): count[A]=2, max_count=3, window="ABBA", (4-3=1 <= 1), length=4

Result: 4

Explanation: We use a sliding window approach. The key insight is that a valid window has at most k characters that are not the most frequent character. We track character frequencies and the maximum frequency in the current window. When (window_size - max_frequency) > k, we need to shrink the window. We maintain the maximum valid window length. Note: we don't need to update max_count when shrinking because we only care about the maximum we've seen, and a smaller window can't have a larger max_count.

Time Complexity: O(n)

Space Complexity: O(1) - at most 26 characters