Sliding Window Pattern

6 min readOptimize contiguous subarrays and subsegments from O(N²) to O(N).

Sliding Window Pattern

The Sliding Window pattern is a powerful algorithmic technique used to track and optimize properties of contiguous subarrays, substrings, or subsegments in linear structures (like arrays or strings). Instead of recomputing each subsegment from scratch in O(N2)O(N^2) or O(NcdotK)O(N cdot K) time, the pattern maintains a running state (representing a "window") and updates it in O(1)O(1) time by incrementally incorporating the new element entering the window from the right and discarding the old element leaving the window from the left.

By avoiding redundant work, Sliding Window transforms expensive brute-force searches into highly efficient O(N)O(N) linear scans.


The Core Concept: Longest Substring Without Repeating Characters

To understand this concept deeply, let's explore Longest Substring Without Repeating Characters.

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

Naive Brute Force — O(N2)O(N^2)

A naive solution checks all possible substrings and determines if each has unique characters:

def lengthOfLongestSubstringBrute(s: str) -> int: max_len = 0 n = len(s) for i in range(n): for j in range(i, n): sub = s[i:j+1] if len(sub) == len(set(sub)): max_len = max(max_len, len(sub)) return max_len

This is highly inefficient because it repeatedly checks overlapping characters from scratch, resulting in a time complexity of O(N2)O(N^2) (or O(N3)O(N^3) depending on string slicing and character set checks).

The Sliding Window Breakthrough — O(N)O(N)

Instead of starting from scratch for each substring, we use a variable-length sliding window represented by two pointers: left and right. We also maintain a hash map (char_map) that stores the most recent index of each character we have processed.

As right expands the window by iterating through the string:

  • If the current character is a duplicate that has already been seen inside our current window boundaries (char_map[char] >= left), we immediately shrink or jump the left pointer to char_map[char] + 1.
  • We then record/update the current character's new index in char_map.
  • We calculate the current window's size (right - left + 1) and update our running maximum length (max_len).

This decision rule allows the window to slide forward and discard invalid subsegments in O(1)O(1) time.


Step-by-Step Traversal

Let's trace the algorithm on the string: "abcabcbb".

Step 1: Initialization

We initialize char_map to store characters' last seen indices, left = 0 to mark the start of the window, and max_len = 0 to track the maximum length of any unique substring.

INITInitialize maximum length max_len = 01 / 54
Input
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
0
max_len
0
Mapping
?
scroll to zoom · drag to pan
154

Step 2: Processing First Three Characters ('a', 'b', 'c')

As right moves from index 0 to 2, the characters 'a', 'b', and 'c' are added to char_map.

  • At right = 0 (character 'a'), the window is ["a"] (length 1), and max_len becomes 1.
SETUpdate max_len: max(0, 1) = 11 / 49
Input
len=1
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
0
max
1
=
1
Variables:
left
0
max_len
1
char
'a'
Mapping
'a'
0
scroll to zoom · drag to pan
149
  • At right = 1 (character 'b'), the window is ["a", "b"] (length 2), and max_len becomes 2.
SETUpdate max_len: max(1, 2) = 21 / 43
Input
len=2
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
1
max
2
=
2
Variables:
left
0
max_len
2
char
'b'
Mapping
'a'
0
'b'
1
scroll to zoom · drag to pan
143
  • At right = 2 (character 'c'), the window is ["a", "b", "c"] (length 3), and max_len becomes 3.
SETUpdate max_len: max(2, 3) = 31 / 37
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
2
max
3
=
3
Variables:
left
0
max_len
3
char
'c'
Mapping
'a'
0
'b'
1
'c'
2
scroll to zoom · drag to pan
137

Step 3: Encountering First Duplicate ('a' at index 3)

At right = 3, we see 'a'. Since 'a' is already in our map at index 0 (which is geleftge \text{left}), we have a duplicate in our current window.

INVALIDDuplicate 'a' found in map at index 0 (>= left pointer 0)1 / 33
Input
len=4
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
0
>=
0
=
true
Variables:
left
0
max_len
3
char
'a'
Mapping
'a'
0
'b'
1
'c'
2
scroll to zoom · drag to pan
133

To resolve this, we contract the window by moving left to char_map['a'] + 1, which is index 1. The window is now ["b", "c", "a"].

CONTRACTContract window: move left pointer left to index 11 / 32
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
1
max_len
3
char
'a'
Mapping
'a'
0
'b'
1
'c'
2
scroll to zoom · drag to pan
132

We update the map to char_map['a'] = 3. The current window length is 3, so max_len remains 3.

SETUpdate lookup dictionary: set char_map['a'] = 31 / 31
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
1
max_len
3
char
'a'
Mapping
'a'
3
'b'
1
'c'
2
scroll to zoom · drag to pan
131

Step 4: Encountering Second Duplicate ('b' at index 4)

At right = 4, we see 'b'. It is in the map at index 1 (geleftge \text{left}), so we have a duplicate.

INVALIDDuplicate 'b' found in map at index 1 (>= left pointer 1)1 / 26
Input
len=4
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
1
>=
1
=
true
Variables:
left
1
max_len
3
char
'b'
Mapping
'a'
3
'b'
1
'c'
2
scroll to zoom · drag to pan
126

We contract the window by moving left to char_map['b'] + 1, which is index 2. The window becomes ["c", "a", "b"].

CONTRACTContract window: move left pointer left to index 21 / 25
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
2
max_len
3
char
'b'
Mapping
'a'
3
'b'
1
'c'
2
scroll to zoom · drag to pan
125

We update char_map['b'] = 4. The current window length is 3, so max_len remains 3.

SETUpdate lookup dictionary: set char_map['b'] = 41 / 24
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
2
max_len
3
char
'b'
Mapping
'a'
3
'b'
4
'c'
2
scroll to zoom · drag to pan
124

Step 5: Encountering Third Duplicate ('c' at index 5)

At right = 5, we see 'c'. It is in the map at index 2 (geleftge \text{left}), so we have a duplicate.

INVALIDDuplicate 'c' found in map at index 2 (>= left pointer 2)1 / 19
Input
len=4
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
2
>=
2
=
true
Variables:
left
2
max_len
3
char
'c'
Mapping
'a'
3
'b'
4
'c'
2
scroll to zoom · drag to pan
119

We contract the window by moving left to char_map['c'] + 1, which is index 3. The window becomes ["a", "b", "c"].

CONTRACTContract window: move left pointer left to index 31 / 18
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
3
max_len
3
char
'c'
Mapping
'a'
3
'b'
4
'c'
2
scroll to zoom · drag to pan
118

We update char_map['c'] = 5. The current window length is 3, so max_len remains 3.

SETUpdate lookup dictionary: set char_map['c'] = 51 / 17
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
3
max_len
3
char
'c'
Mapping
'a'
3
'b'
4
'c'
5
scroll to zoom · drag to pan
117

Step 6: Encountering Fourth Duplicate ('b' at index 6)

At right = 6, we see 'b'. It is in the map at index 4 (geleftge \text{left}), so we have a duplicate.

INVALIDDuplicate 'b' found in map at index 4 (>= left pointer 3)1 / 12
Input
len=4
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
4
>=
3
=
true
Variables:
left
3
max_len
3
char
'b'
Mapping
'a'
3
'b'
4
'c'
5
scroll to zoom · drag to pan
112

We contract the window by moving left to char_map['b'] + 1, which is index 5. The window becomes ["c", "b"] (length 2).

CONTRACTContract window: move left pointer left to index 51 / 11
Input
len=2
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
5
max_len
3
char
'b'
Mapping
'a'
3
'b'
4
'c'
5
scroll to zoom · drag to pan
111

We update char_map['b'] = 6. The current window length is 2, so max_len remains 3.

SETUpdate lookup dictionary: set char_map['b'] = 61 / 10
Input
len=2
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
5
max_len
3
char
'b'
Mapping
'a'
3
'b'
6
'c'
5
scroll to zoom · drag to pan
110

Step 7: Encountering Fifth Duplicate ('b' at index 7)

At right = 7, we see 'b'. It is in the map at index 6 (geleftge \text{left}), so we have a duplicate.

INVALIDDuplicate 'b' found in map at index 6 (>= left pointer 5)1 / 5
Input
len=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
6
>=
5
=
true
Variables:
left
5
max_len
3
char
'b'
Mapping
'a'
3
'b'
6
'c'
5
scroll to zoom · drag to pan
15

We contract the window by moving left to char_map['b'] + 1, which is index 7. The window becomes ["b"] (length 1).

CONTRACTContract window: move left pointer left to index 71 / 4
Input
len=1
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
7
max_len
3
char
'b'
Mapping
'a'
3
'b'
6
'c'
5
scroll to zoom · drag to pan
14

We update char_map['b'] = 7. The current window length is 1, so max_len remains 3.

SETUpdate lookup dictionary: set char_map['b'] = 71 / 3
Input
len=1
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
7
max_len
3
char
'b'
Mapping
'a'
3
'b'
7
'c'
5
scroll to zoom · drag to pan
13

Step 8: Done

The loop terminates, and we return the final maximum length max_len = 3.

DONEDone: Return final max_len = 3. Longest substring is "abc"1 / 1
Input
max=3
a
0
b
1
c
2
a
3
b
4
c
5
b
6
b
7
Active Operation:
a
op
b
=
res
Variables:
left
0
max_len
3
Mapping
'a'
3
'b'
7
'c'
5
scroll to zoom · drag to pan
11

Code Implementation

def lengthOfLongestSubstring(s: str) -> int: char_map = {} left = 0 max_len = 0 for right in range(len(s)): char = s[right] # If the character is already in the map and within the current window if char in char_map and char_map[char] >= left: left = char_map[char] + 1 # Contract window by jumping left char_map[char] = right # Record/update character index max_len = max(max_len, right - left + 1) return max_len

Time Complexity: O(N)O(N) — we scan the string with the right pointer once, and the left pointer only moves forward, visiting each element at most once. Space Complexity: O(min(N,M))O(\min(N, M)) — where MM is the size of the character set, as we store at most MM unique characters in the map.


Core Variations

1. Fixed-Size Sliding Window

The window size is constant. We expand the window to size KK initially, and then for each subsequent step, we add the new element on the right and remove the oldest element on the left to maintain the exact size KK.

  • Use when: searching for properties of subarrays of a fixed, predetermined length.
  • Examples: Maximum Sum Subarray of Size K, Permutation in String.

2. Variable-Size Sliding Window (Dynamic Expansion)

The window grows and shrinks dynamically based on a constraint. We expand the right pointer to include new elements until the constraint is violated, and then we shrink the left pointer until the constraint is satisfied again.

  • Use when: searching for the longest/shortest subarray satisfying a dynamic constraint.
  • Examples: Longest Substring Without Repeating Characters, Minimum Size Subarray Sum.

3. Sliding Window with Auxiliary Tracker

In addition to pointers, we use a hash map, frequency array, or set to track counts, frequencies, or indices of elements currently inside the window.

  • Use when: window validity depends on character counts or frequencies.
  • Examples: Longest Repeating Character Replacement, Minimum Window Substring.

When Do I Use This?

Check if the problem satisfies these conditions:

  1. The input is a linear structure like an array, string, or linked list.
  2. The problem asks for a contiguous subarray, substring, or subsegment (e.g., "longest", "shortest", "maximum", "minimum").
  3. Checking all possible contiguous subarrays takes O(N2)O(N^2) or O(N3)O(N^3) time, but window state updates can be done incrementally in O(1)O(1) time.

If these apply, Sliding Window is the optimal pattern.


Practice Problems

How helpful was this content?

Comments

0/2000

Sign in to join the discussion