Skip to content

Beginner: foundations · Hashing, two pointers and sliding window · topic 15 of 61 · 40 min

Sliding window: variable size

Explanation

When the question asks for the longest or shortest run that obeys a rule, the window grows and shrinks. Move the right edge forward one item at a time, and while the window breaks the rule, move the left edge forward. Each edge moves at most n times, so it is O(n). It solves the longest substring without repeats, the shortest run reaching a target sum (positive numbers only) and the longest run with at most k distinct characters.

When the window size is not given

Some questions ask for the LONGEST or SHORTEST run that satisfies a rule: the longest stretch of a string with no repeated character, or the shortest run of positive numbers whose sum reaches a target. The window size is now part of the answer, so the window has to grow and shrink.

The pattern: move the right edge forward one item at a time (grow). While the window breaks the rule, move the left edge forward (shrink). When the loop ends, the window is valid again, so update the answer.

output
left = 0
for right in range(len(items)):
    add items[right] to the window
    while the window is invalid:
        remove items[left] from the window
        left += 1
    answer = best(answer, right - left + 1)
The template in plain words. For "shortest" problems the answer is updated inside the while loop instead.

Why it is O(n), not O(n²)

A while loop inside a for loop looks quadratic. But left only moves forward and never passes right, so across the WHOLE run left moves at most n times. Every item is added once and removed at most once: O(n) in total. Counting total moves instead of moves per loop is amortised analysis, from Module 1.

Longest substring without repeating characters

The rule is "no character appears twice in the window". Keep a set of the characters in the window. When s[right] is already in the set, remove s[left] and move left on until the repeat is gone, then add s[right]. O(n) time; the set holds at most one entry per distinct character.

python
def longest_unique(s):
    seen = set()
    left = best = 0
    for right, c in enumerate(s):
        while c in seen:
            seen.remove(s[left])
            left += 1
        seen.add(c)
        best = max(best, right - left + 1)
    return best

print(longest_unique("abcabcbb"))   # Output: 3  ("abc")
print(longest_unique("pwwkew"))     # Output: 3  ("wke")

A faster variant remembers the last index of each character and jumps left straight past the earlier copy: left = max(left, last[c] + 1). The max matters. In "abba", when the second "a" arrives, its earlier copy at index 0 is already outside the window (left is 2), and jumping to 0 + 1 would move left backwards and make the window wrong.

Shortest subarray with sum at least target

With positive numbers, adding an item always raises the sum and removing one always lowers it. So grow right until the sum reaches the target; then, while it stays at or above the target, record the length and shrink from the left to look for something shorter. O(n) time, O(1) space.

python
def min_len(target, nums):
    left = total = 0
    best = None
    for right, x in enumerate(nums):
        total += x
        while total >= target:
            if best is None or right - left + 1 < best:
                best = right - left + 1
            total -= nums[left]
            left += 1
    return best if best is not None else 0

print(min_len(7, [2, 3, 1, 2, 4, 3]))   # Output: 2  ([4, 3])

At most k distinct characters

Keep a dict from character to its count in the window. Add s[right]. While the dict has more than k keys, lower the count of s[left], delete the key when it reaches 0, and move left on. The answer is the largest window seen. O(n) time, and O(k) space for the dict.

Why it fails with negative numbers

The method relies on one promise: shrinking a valid window can only make it "less valid", and growing an invalid one can only make it "more invalid". With negative numbers that promise breaks. Removing a negative item RAISES the sum, so "shrink while the sum is at least the target" can stop too early or never see the answer.

python
print(min_len(6, [1, -5, 6]))   # Output: 0
# Wrong: [6] alone reaches 6, so the right answer is 1.
# The running total goes 1, -4, 2 and never reaches 6.

For sums with negative numbers use prefix sums instead: a hash map for "sum exactly k" (Topic 2), or a monotonic deque over prefix sums for "shortest with sum at least k" (deques come in Module 4).

Common mistakes

  • Shrinking with if instead of while, so the window can stay invalid. Fix: shrink with while until it is valid again.
  • Updating the answer at the wrong moment. Fix: for "longest", update after shrinking; for "shortest", update inside the shrinking loop.
  • Letting left jump backwards with a last-index map ("abba"). Fix: left = max(left, last[c] + 1).
  • Forgetting to delete keys whose count is 0, so len(counts) overcounts the distinct characters. Fix: del counts[c] when it reaches 0.
  • Using the variable window on numbers that can be negative. Fix: prefix sums with a map.
  • Returning float("inf") or None when no window works. Fix: return what the task asks for, often 0.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Sliding window: variable size · 1/8

Sliding window: variable size

This lesson is about variable-size sliding windows, for the longest or shortest run that obeys a rule.

Prefer a video by a teacher? Find free videos on “variable size sliding window longest substring without repeating characters minimum size subarray sum” on YouTube ↗

In real life: Flagging a burst of transfers

A bank's monitoring rules may look for the shortest run of consecutive transfers from one account whose total crosses a limit, because many transfers in quick succession can be a warning sign. Amounts are positive, so a variable sliding window finds the shortest run in one pass. Real systems use time windows and many more signals; this shows only the core idea.

python
amounts = [2000, 15000, 8000, 30000, 5000, 20000, 1000]
limit = 50000
left = total = 0
best = None
for right, x in enumerate(amounts):
    total += x
    while total >= limit:
        if best is None or right - left + 1 < best:
            best = right - left + 1
        total -= amounts[left]
        left += 1
print(best)
Shows 3: three transfers in a row (15000 + 8000 + 30000) already cross Rs 50,000.

Practice (0/3 done)

Task 1 · Code · Python

Write length_of_longest_substring(s) that returns the length of the longest substring (a run of neighbouring characters) with no repeated character. Case matters and spaces are characters. O(n) time with a sliding window. Example: length_of_longest_substring("abcabcbb") returns 3.

Task 2 · Code · Python

nums contains only POSITIVE integers. Write min_subarray_len(target, nums) that returns the length of the shortest run of neighbouring numbers whose sum is at least target, or 0 if there is none. O(n) time with a variable sliding window. Example: min_subarray_len(7, [2, 3, 1, 2, 4, 3]) returns 2 ([4, 3]).

Task 3 · Code · Python

Write longest_k_distinct(s, k) that returns the length of the longest substring of s containing AT MOST k different characters. If k is 0, the answer is 0. O(n) time with a dict of counts. Example: longest_k_distinct("eceba", 2) returns 3 ("ece").

Quick quiz

Question 1

1.Why is the variable sliding window O(n) even with a while loop inside the for loop?

Question 2

2.Longest substring without repeats on "abcb": what is the window right after the second "b" is added and the repeat is removed?

Question 3

3.Why must the numbers be positive for the shortest-subarray-with-sum-at-least-target window?

Question 4

4.With a last-index map, why write left = max(left, last[c] + 1)?

Question 5

5.An at-most-k-distinct window has counts {"a": 2, "b": 0, "c": 1}. How many different characters are really in the window?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why is the variable sliding window O(n) even with a while loop inside the for loop?

Answer: left only moves forward, so it moves at most n times in total

Across the whole run each item is added once and removed at most once.

Longest substring without repeats on "abcb": what is the window right after the second "b" is added and the repeat is removed?

Answer: "cb"

Seeing the second b, the window drops a, then the first b, leaving "cb".

Why must the numbers be positive for the shortest-subarray-with-sum-at-least-target window?

Answer: So removing an item from the left always lowers the sum, which makes "shrink while valid" correct

With negatives, removing an item can raise the sum, so the window can stop shrinking too early or miss the answer.

With a last-index map, why write left = max(left, last[c] + 1)?

Answer: So left never moves backwards when the earlier copy is already outside the window

In "abba", the second a's earlier copy is at 0 while left is already 2; jumping to 1 would be wrong.

An at-most-k-distinct window has counts {"a": 2, "b": 0, "c": 1}. How many different characters are really in the window?

Answer: 2

b has count 0, so it is not in the window. Forgetting to delete it makes len(counts) say 3.

The window [4, 2, 7] has sum 13. The next item is 1 and k = 3. What is the next window sum?Related

Answer: 10

13 − 4 (leaves) + 1 (enters) = 10, the sum of [2, 7, 1].

From Sliding window: fixed size

Why is a sliding window wrong for "count subarrays with sum exactly k" when numbers can be negative?Related

Answer: Adding a number may make the sum go down, so you cannot decide when to shrink

With negatives the sum is not monotonic as the window grows; prefix sums with a hash map handle it.

From Recognising the pattern

In the subarray-sum pattern, why look up running − k BEFORE storing running?Related

Answer: So a prefix is never matched with itself, which would count an empty subarray when k = 0

If running were stored first, running − 0 would find the prefix just stored.

From Counting and grouping with hashing

Which pattern fits "find two numbers that add up to a target" in unsorted data?Related

Answer: Hash map

Store what you have seen and look up the complement.

From Data-structure questions in JavaScript

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next: Stacks →

Next steps

  1. This is the last topic of “Hashing, two pointers and sliding window”. The module exam comes next.
  2. Module 3 exam: Hashing, two pointers and sliding window · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Stacks, queues and linked lists. 61 topics left in the course before the final exam.

← Previous: Sliding window: fixed size