Pebellslearn by building
0%
RoadmapMatrixCatalogSandboxSign in
Foundations: Complexity & Arrays  ›  Lesson

Two Pointers & Sliding Window

Two Pointer Technique 22 minTwo moving indexes instead of a loop inside a loop
You're building a piece ofTunebox — the library
This piece — session_within(): Finds a run of tracks that fits a time budget.
Scenario A listener taps "30 minutes of music". The session finder slides a window along their queue to find the longest back-to-back run that fits — one O(n) pass instead of checking every start and end.
Your task
Build session_within(durations, limit). Find the longest run of back-to-back tracks whose total minutes are at most limit, and return it as [start, end] with end exclusive. Ties go to the earliest run; if no track fits, return [0, 0]. Every duration is positive. Example: session_within([3, 1, 2, 1], 4) → [1, 4].

Two Pointers & Sliding Window

"Give me 30 minutes of music." To answer, Tunebox needs a run of back-to-back tracks that fits the time. The obvious way tries every start with every end — O(n²) pairs, so 10,000 tracks means 50 million checks. This lesson gets the same answers in one pass: instead of one index looping inside another, two indexes move through the array together, and neither ever turns back.

The brute force, and what it wastes

Find two tracks that exactly fill a 9-minute gap before the news:

python

The inner loop learns nothing from the sort. Once 2 + 8 is too big, every pair containing the 8 is too big — 2 is the shortest partner it will ever get. That track could be thrown away after a single check.

Pointers from both ends

Put lo on the shortest track and hi on the longest, and look at their sum:

  • too small → only a longer partner for lo can help: move lo right.
  • too bighi is too long for every track left: move hi left.
  • exactly right → done.

Every step discards one track for good, so it takes at most n steps: O(n) time, O(1) extra space.

step through it
1mins = [2, 3, 4, 6, 7, 8]
2gap = 9
3lo, hi = 0, len(mins) - 1
4while lo < hi:
5 total = mins[lo] + mins[hi]
6 if total == gap:
7 break
8 if total < gap:
9 lo += 1
10 else:
11 hi -= 1
12print(mins[lo], mins[hi])

❓ Cross-question — "How do I know it never skips the right pair?" hi only moves left when mins[lo] + mins[hi] is too big. Every partner still available is at least as long as mins[lo], so that hi track was too long for all of them — it couldn't be in any answer. The same argument covers lo. A pointer only ever leaves behind tracks that can't be part of a pair.

What does this print?
mins = [1, 3, 4, 5, 9]
lo, hi = 0, len(mins) - 1
steps = 0
while lo < hi:
    steps += 1
    total = mins[lo] + mins[hi]
    if total == 8:
        break
    if total < 8:
        lo += 1
    else:
        hi -= 1
print(mins[lo], mins[hi], steps)

The same shape — start at both ends, walk inward — reverses a list in place and checks for palindromes:

python

Both ends must start on real items:

broken — fix itIndexError

"level" reads the same both ways, so this should print True. It crashes on the very first comparison instead.

Same direction: a read pointer and a write pointer

Pointers don't have to face each other. To remove skipped plays (0 minutes) in place, read visits every slot and write marks where the next keeper goes:

What does this print?
plays = [3, 0, 0, 4, 0, 5]
write = 0
for read in range(len(plays)):
    if plays[read] != 0:
        plays[write] = plays[read]
        write += 1
print(plays)

write never overtakes read, so nothing is overwritten before it has been read. That's O(n) time and O(1) space — calling plays.remove(0) in a loop would be O(n) per removal, O(n²) in all.

python

Fixed-size sliding window

"Which 3 back-to-back tracks have the most plays?" Re-summing every window is O(n·k). But neighbouring windows share all but two tracks, so slide instead: add the track that enters and subtract the one that leaves.

python

Getting the leaving index right is the whole trick:

broken — fix it

This should print 18, the best total of 3 neighbouring tracks. It prints 20 — the wrong track is leaving the window, so "window" stops being a window.

Variable-size sliding window

The window doesn't need a fixed size. For "the longest run of tracks within a time limit", let it grow on the right while it fits and shrink on the left when it doesn't:

python

Why it's O(n), not O(n²): there is a loop inside a loop, but the inner one only moves start forward, and start never goes back. Across the whole run end moves n times and start at most n times: 2n steps. The cost is amortized over the pass, just like append.

❓ Cross-question — "What if a duration could be negative?" Then it breaks. Shrinking assumes that dropping a track never raises the total and adding one never lowers it. With negative numbers a window over the limit could come back under by adding a track, so throwing away its left side can lose the answer. Zero-minute tracks are fine; negatives need a different technique.

Complexity at a glance

task brute force with pointers
pair with a given sum (sorted input) O(n²) O(n) time, O(1) space
reverse / palindrome check O(n) time, O(n) copy O(n) time, O(1) space
remove items in place O(n²) with remove O(n) time, O(1) space
best window of size k O(n·k) O(n)
longest window under a limit O(n²) O(n)

Coming from Java/JS: nothing here is Python-specific — while (lo < hi) reads the same everywhere. Python just adds the swap without a temp variable: a[lo], a[hi] = a[hi], a[lo].


Idioms & real-world patterns

When the input isn't sorted

Pointers from both ends need sorted data. Sorting costs O(n log n) — still far better than O(n²). If you need the original positions, sort the indexes instead of the values. (A hash table finds the pair in one O(n) pass with no sort, at the price of O(n) memory — that's next lesson.)

python

Neighbours with pairwise

Comparing each track with the next is two pointers one step apart. itertools.pairwise does it without indexes or slice copies:

python

⚡ Advanced — windows that count instead of sum

Plenty of window problems count what's inside — "the longest run with at most 2 different artists". The shape is identical; only the "is this window still valid?" test changes, and a dict of counts keeps it O(1):

python

🎯 Your turn

Build the session finder: session_within(durations, limit) returns the longest run of back-to-back tracks whose total minutes are at most limit, as [start, end] with end exclusive. Ties go to the earliest run; if no track fits, return [0, 0]. Every duration is positive.

  • session_within([5, 5, 5, 5], 12)[0, 2]
  • session_within([3, 1, 2, 1], 4)[1, 4]

Hint — it's longest_within from above, but remember where the best window was, not just how long it was — and only replace it when a window is strictly longer.

Then press ▶ Run, tap the Live App try chips to call it with different inputs, and hit ✓ Check. Green = this piece of Tunebox is built. ✅

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upMirrored playlist

A DJ set is mirrored when its track list reads the same forwards and backwards. Write is_mirrored(titles) returning True or False, comparing from both ends with two pointers (no reversed copy).

two-pointers
DrillDedupe a sorted list in place

mins is sorted, so repeated durations sit next to each other. Write dedupe_sorted(mins) that keeps one of each duration using a read pointer and a write pointer in place, then returns mins[:write] — the unique durations in order.

two-pointersin-place
BuildFill the gap

mins is sorted shortest first. Write fill_gap(mins, gap) returning [a, b] — the minutes of two different tracks with a + b == gap and a <= b — or [] if no pair exists. If several pairs work, return the one with the smallest a. Use pointers from both ends.

two-pointers
BossShortest run that lasts

A workout needs at least target minutes of back-to-back music. Write shortest_run(mins, target) returning the length (number of tracks) of the shortest run whose minutes total at least target, or 0 if even the whole list falls short. Every duration is positive and target ≥ 1. Aim for O(n).

sliding-window
CapstoneShortest mix with every artist

artists[i] is the artist of track i in the queue. Write shortest_mix(artists, wanted) returning [start, end] (end exclusive) of the shortest run of back-to-back tracks that includes every artist in wanted (a non-empty list) at least once. Ties go to the earliest run; if no run works, return [0, 0].

sliding-windowdictset
session_within.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
session_within(durations, limit) → list[start, end) of the longest run of tracks that fits the time limit.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.