Skip to content

Two Pointers & Sliding Window

In one sentence

Use two indices that only move forward, so together they take \(O(n)\) steps instead of the \(O(n^2)\) of trying every pair.

1. What problem does it solve?

Example — 279B · Books (3-TwoPointers · B, rating 1400)

Valera has \(t\) free minutes and \(n\) books; book \(i\) takes \(a_i\) minutes. He picks a starting book and reads books \(i, i+1, i+2, \dots\) in order, and only starts a book if he can finish it. What is the largest number of books he can read?

In other words: the longest run of consecutive books with total time \(\le t\).

Limits: \(n \le 10^5\), \(a_i \le 10^4\), \(t \le 10^9\).

Input        Output
4 5
3 1 2 1      3

3 3
2 2 3        1

First test: books 1 2 1 take \(4 \le 5\) minutes.

Brute force: try every start \(l\) and extend \(r\) while the sum fits. That is \(O(n^2) = 10^{10}\) in the worst case.

Key observation: all times are positive. So if the window \([l, r]\) fits, every smaller window inside it fits too. When we move \(r\) to the right, the best \(l\) for the new \(r\) is never to the left of the best \(l\) for the old \(r\). Both pointers only move right.

2. The math

2.1 Sliding window (same direction)

for right = 0 .. n-1:
    1. add a[right] to the window
    2. while the window is invalid: remove a[left], left += 1
    3. the window [left, right] is valid, so update the answer

Why it is \(O(n)\): right moves \(n\) times. left also moves at most \(n\) times in total, because it never moves back. So step 2 runs at most \(n\) times summed over the whole loop, even though it is a while inside a for. That is \(O(n)\) amortized.

Why it is correct: for each right, the loop stops at the smallest left whose window fits (the "fits ⇒ smaller windows fit" property). So \([left, right]\) is the longest fitting window ending at right, and the best over all right is the answer.

Trace on 3 1 2 1 with \(t = 5\) (0-indexed):

right add sum after adding shrink? window length best
0 3 3 no 3 1 1
1 1 4 no 3 1 2 2
2 2 6 \(6 > 5\): drop 3 → 3 1 2 2 2
3 1 4 no 1 2 1 3 3

2.2 Opposite ends

1669F · Eating Candies (4-Misc · C, rating 1100)

\(n\) candies with weights \(w_i \ge 1\) lie in a row. Alice eats some candies from the left, Bob some from the right, and no candy is eaten twice. They must eat the same total weight. What is the largest total number of candies they can eat?

Input                    Output
4
3
10 20 10                 2
6
2 1 4 2 4 1              6
5
1 2 4 8 16               0
9
7 3 20 5 15 1 11 8 10    7

Put i at the left end and j at the right end, with sums a (Alice) and b (Bob):

  • a <= b: Alice is behind, so she eats the next candy from the left (i += 1).
  • a > b: Bob eats the next candy from the right (j -= 1).
  • Whenever a == b, record \(i + (n - 1 - j)\) candies.
  • Stop when the pointers cross.

Why nothing is missed. Weights are positive, so Alice's possible totals (\(w_1\), \(w_1 + w_2\), …) form a strictly increasing list, and so do Bob's. We need a value that appears in both lists. Always advancing the smaller one is exactly how you merge two sorted lists, and a merge never skips a common value. Later equal pairs use more candies, so the last one recorded is the best.

Each step moves one pointer inward, so there are at most \(n\) steps.

2.3 When does it apply?

pattern pointers the property you need
longest / shortest subarray with a condition same direction (window) fits ⇒ smaller (or larger) windows fit
count subarrays with sum \(\le K\) (non-negative values) same direction adding elements only increases the sum
pair with a target sum on a sorted array opposite ends moving one end changes the sum in one direction
equal prefix and suffix sums, merging sorted lists one pointer per list both lists sorted

3. Template

From our slides (USACO Algorithm — From Bronze to Silver, 16 Jan): the longest subarray with sum \(\le K\). This is Books with K = t.

left = 0
current_sum = 0
max_len = 0
K = 10
nums = [3, 1, 2, 7, 4]

for right in range(len(nums)):
    # 1. Add new element
    current_sum += nums[right]

    # 2. Fix the condition (Shrink window)
    while current_sum > K:
        current_sum -= nums[left]
        left += 1

    # 3. Update Answer
    max_len = max(max_len, right - left + 1)

print(max_len)

For nums = [3, 1, 2, 7, 4] and K = 10 it prints 3 (3 1 2 has sum \(6\), and 7 cannot join without the sum going over \(10\)). The whole algorithm is the three commented steps; only "add", "invalid", and "remove" change between problems.

4. Worked solution — Books

The slide template, reading n, t and the list from input.

import sys

input = sys.stdin.readline


def solve():
    n, t = map(int, input().split())
    a = list(map(int, input().split()))

    left = 0
    current_sum = 0
    max_len = 0

    for right in range(n):
        # 1. add a new book
        current_sum += a[right]

        # 2. drop books from the left until the time fits
        while current_sum > t:
            current_sum -= a[left]
            left += 1

        # 3. update the answer
        max_len = max(max_len, right - left + 1)

    print(max_len)


solve()

5. Worked solution — Eating Candies

import sys

input = sys.stdin.readline


def solve():
    n = int(input())
    w = list(map(int, input().split()))

    i = 0
    j = n - 1
    a = 0
    b = 0
    best = 0

    while i <= j:
        if a <= b:
            a += w[i]
            i += 1
        else:
            b += w[j]
            j -= 1
        if a == b:
            best = i + (n - 1 - j)

    print(best)


t = int(input())
for _ in range(t):
    solve()

Trace on 2 1 4 2 4 1:

step eat a b equal? best
1 Alice 2 2 0 0
2 Bob 1 2 1 0
3 Bob 4 2 5 0
4 Alice 1 3 5 0
5 Alice 4 7 5 0
6 Bob 2 7 7 yes \(3 + 3 = 6\)

6. Common mistakes

Using if instead of while to shrink

One removal may not be enough. Shrink until the window is valid again.

Sliding window with negative numbers

"Sum \(\le K\)" windows need non-negative values. With negatives, adding an element can make the sum smaller, so the monotone property is gone. Use prefix sums instead.

Checking a == b only at the end

In Eating Candies the best moment is usually in the middle. Record the answer every time the sums match.

while i < j in Eating Candies

The last candy (when i == j) may still be eaten by one of them. Use i <= j.

7. Practice

Problem Set Rating Idea
279B · Books 3-TwoPointers · B 1400 the slide template (this page)
1669F · Eating Candies 4-Misc · C 1100 opposite ends (this page)
1791C · Prepend and Append 3-TwoPointers · A 800 shrink from both ends
1133C · Balanced Team 3-TwoPointers · C 1200 sort + window with max − min ≤ 5
1354B · Ternary String 3-TwoPointers · D 1200 shortest window containing 1, 2, 3
701C · They Are Everywhere 3-TwoPointers · E, 11-review · B 1500 shortest window with every type
1199C · MP3 7-Intervals · E 1600 sort + window over distinct values

Set 11 (review) also has 702C · Cellular Network, 1777C · Quiz Master, 814C · An impassioned circulation of affection and 1744F · MEX vs MED.


Credits & licenses
  • Section structure follows USACO Guide — Two Pointers by Darren Yao, Qi Wang, Ryan Chou and David Zhou, licensed CC BY-NC-SA 4.0.
  • The template in §3 is from our slides. Everything else (amortized argument, merge argument, traces, worked solutions, mistakes) is ours.