E ExamMaster

Data Structures & Algorithms · Data Structures & Algorithms

Two Pointers and Sliding Window

Two pointer traversal and sliding window techniques for subarray and substring problems.

Eight concepts on two pointers and sliding windows — opposite-end scans on sorted arrays, fixed and variable windows, the classic longest-unique-substring walk, and fast/slow cycle detection. The shared win is O(n) time with O(1) extra memory when the pointers only move forward (or inward) once.

  • Data Structures & Algorithms
  • Medium level
  • 8 concepts
  • 5 practice questions

1Two indices instead of nested loops

Two-pointer techniques maintain two indices into a sequence and move them according to a rule — toward each other, or both forward at possibly different speeds. The goal is to solve pair, partition, or window problems in O(n) time instead of an O(n^2) double loop that re-checks the same pairs.

The method is not free magic: opposite-end pair search needs sorted order (or a sort that costs O(n \log n) first). Sliding windows need a constraint you can maintain by growing and shrinking a contiguous span. When those ingredients are present, each element is touched a constant number of times.

Figure. Two indices replace a nested scan when the array's order lets you advance exactly one side per step.

Why it beats O(n^2)

  1. One pass familyPointers only advance (or only retreat); they never restart from the beginning for each i.
  2. Amortized linearAcross the whole run, left and right each move at most n steps → O(n) total work.
  3. SpaceOften O(1) extra — just the indices (plus a small frequency map for some windows).
A two-pointer scan where each index only moves forward across an n-element array is
  1. O(n) time, because each pointer contributes at most n steps
  2. O(n^2) time, because two indices always mean a nested loop
  3. O(1) time, because only two integers are stored

Two indices are O(1) space, not O(1) time. Forward-only movement caps total steps at O(n); nested loops are the pattern this replaces.

2Opposite-end pointers on a sorted array

On a sorted array, place left at the start and right at the end. Compare the sum (or other monotone combination) to the target: if too small, advance left to increase the sum; if too large, move right leftward to decrease it; if equal, you have the pair. Each step discards one index permanently.

Unsorted data must be sorted first — O(n \log n) — or you switch to a hash map that spends O(n) extra memory. Opposite-end exploits sortedness specifically to keep extra memory at O(1).

Figure. Start with L on 1 and R on 11. Sums that are too large shrink R; sums that are too small advance L until 4 + 5 = 9.

Pair sum inward

  1. Initializeleft = 0, right = n − 1 on a sorted array.
  2. Compare sumIf nums[left] + nums[right] < target, left++; if greater, right−−; if equal, return the pair.
  3. StopPointers meet ⇒ no pair. Total movements ≤ n.

Two Sum on a Sorted Array

Given a sorted array [1, 3, 4, 5, 7, 11] and target 9, find two elements that sum to the target.

  • left = 0, right = 5: 1 + 11 = 12 > 9 → right−−right = 4
  • 1 + 7 = 8 < 9 → left++left = 1
  • 3 + 7 = 10 > 9 → right−−; then 3 + 5 = 8 < 9 → left++left = 2, right = 3
  • 4 + 5 = 9pair (4, 5) in O(n) time, O(1) space

Pro tip. Opposite-end two pointers exploit sortedness to avoid the O(n) extra space a hash map would use.

On a sorted array, if nums[left] + nums[right] is less than the target, you should
  1. Advance left, because the sum needs to grow
  2. Advance right, because a larger right index always increases the sum
  3. Reset both pointers to the middle and binary search

Sorted ascending: moving left toward larger values increases the sum; moving right would decrease it further. Binary search of the whole array each time would rebuild an O(n \log n) nested pattern.

3Sliding window: grow right, shrink left

A sliding window maintains a contiguous subarray or substring [\mathrm{left}, \mathrm{right}]. Expand by advancing right to include new elements; when a constraint breaks, advance left to drop elements from the front until the window is valid again. Track a best answer (length, sum, count) at each valid state.

Because left and right only move forward, each index enters and leaves the window at most once — total work O(n) even though a nested description sounds quadratic.

Figure. A contiguous window on "abcabcbb". Grow R to include; when a duplicate enters, advance L until the window is unique again.

Variable window skeleton

  1. Expandright++ and update the window's summary (sum, set, counts).
  2. Shrink while invalidWhile the constraint fails, remove s[left] from the summary and left++.
  3. RecordUpdate the best answer from the current valid window, then continue expanding.
In a variable sliding window on a string of length n, total pointer movement is
  1. O(n), because left and right each advance at most n times
  2. O(n^2), because every right step restarts left at 0
  3. O(1), because the window is a single object

Left never moves backward; right never moves backward. At most 2n advances total. Restarting left each time would destroy the amortization.

4Fixed-size vs variable-size windows

A fixed window of length k always covers k elements: slide by dropping the outgoing element and adding the incoming one, usually in O(1) work per step after the first window is built. Use it for "best subarray of length exactly k".

A variable window grows and shrinks to satisfy a predicate — longest substring with all unique characters, shortest subarray with sum at least S. The length is the answer (or part of it), not an input. Mixing the two usually means you hardcoded k on a problem that asked for an extremal length.

Same strip shape as Sliding window: grow right, shrink left — fixed windows hold length k; variable windows let length be the answer while the constraint holds.

Window kinds
KindLengthTypical ask
FixedGiven kMax/min aggregate among windows of size k
VariableDiscoveredLongest/shortest window meeting a constraint
"Longest substring with all distinct characters" is
  1. A variable window — length is what you maximize under a constraint
  2. A fixed window — you are given k up front
  3. Not a window problem — only opposite-end pointers apply

No k is given; you expand/shrink until uniqueness holds and track the max length. Fixed windows need a stated size.

5Longest substring without repeating characters

Maintain window [\mathrm{left}, \mathrm{right}] and a set (or last-seen map) of characters inside it. Advance right; if s[\mathrm{right}] is already in the window, advance left — removing characters as you go — until that character is unique again. After each expansion, the window length is a candidate maximum.

On s = \texttt{abcabcbb} the answer is 3 ("abc"). The run is O(n) because each pointer only moves forward.

Track the window on the Sliding window: grow right, shrink left strip: grow while characters are new, shrink from the left on a repeat, and keep the best length seen.

Shrink on duplicate

  1. Expand rightInclude s[right] in the window structure.
  2. RepairWhile s[right] duplicates a window character, drop s[left] and left++.
  3. Track maxbest = max(best, right − left + 1).

Variable window with a set

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

Longest Substring Without Repeating Characters

Given s = "abcabcbb", find the length of the longest substring with all distinct characters.

  • right on "abc": seen = {a,b,c}len = 3; best = 3
  • next "a" ∈ seen → remove s[left]="a", left→1; then add "a"window "bca"; len = 3; best = 3
  • at final "bb": shrink until one "b" remainsbest stays 3 (never exceeds "abc")

Pro tip. This variable-size window runs in O(n) because each pointer only moves forward across the string once.

Coding lab. Longest unique window in abcabcbb runs in the app, with checks on your output.

For s = "abcabcbb", the longest substring without repeating characters has length
  1. 3, e.g. "abc"
  2. 8, the whole string
  3. 2, because "bb" appears at the end

"abc" (and later "bca", "cab") are length 3; the full string repeats. The final "bb" is a short unique window of length 1 after shrinking.

6Fast and slow pointers

Fast/slow (Floyd) uses two pointers at different speeds on a linked structure: typically slow moves one step while fast moves two. If there is a cycle, fast eventually laps slow and they meet inside the loop. If fast hits null, there is no cycle.

The same idea finds a midpoint in one pass: when fast reaches the end, slow sits at the middle — useful before reversing the second half of a list. Speed difference, not sortedness, is the ingredient.

Figure. A→B→C→D→E with E linking back to C along the outside. Fast and slow start at A; they first meet at D in the ledger's three-tick walk.

Cycle detection

  1. Start togetherslow = fast = head.
  2. Advanceslow = slow.next; fast = fast.next.next (when safe).
  3. DecideMeet ⇒ cycle. fast becomes null ⇒ acyclic.

Meet inside a cycle

A list has nodes A→B→C→D→E→C (E links back to C). Slow starts at A and moves one step per tick; fast moves two. Do they meet, and where is the first meeting node among {C, D, E}?

  • After tick 1: slow = B, fast = Cnot met
  • Tick 2: slow = C, fast = Enot met
  • Tick 3: slow = D, fast = D (E→C→D)meet at D

Pro tip. Meeting proves a cycle exists; a second phase (reset one pointer to head, step both by one) finds the cycle entrance if you need it.

In Floyd cycle detection, if the list has no cycle, fast
  1. Reaches null (or null.next) and the algorithm reports no cycle
  2. Loops forever because two pointers always meet
  3. Must jump to the head and start a binary search

On an acyclic list fast falls off the end. Meeting is the cycle signal; binary search does not apply to an unmarked linked list.

7Why windows are amortized O(n)

Fixed windows of size k: build the first window in O(k), then each slide removes one element and adds one — O(1) per step, O(n) over the array. Variable windows: every index is assigned to right once and to left at most once, so expand/shrink work sums to O(n) even if an inner while-loop looks scary in isolation.

Opposite-end two pointers share the same accounting: left only increases, right only decreases, at most n moves total. Sorting first, when required, dominates at O(n \log n).

Figure. Amortized O(n): every index is enqueued at most once and dequeued at most once across the whole scan.

Charge each move once

  1. Right advancesAt most n expansions.
  2. Left advancesAt most n shrinks — left never retreats.
  3. TotalO(n) updates of the window summary (plus O(n \log n) if you sorted first).
Cost summary
PatternTimeExtra space
Opposite-end on sorted dataO(n) after sortO(1)
Sort then opposite-endO(n \log n)O(1) or sort's stack
Variable / fixed windowO(n)O(1) or O(k) for counts
Fast/slow on a listO(n)O(1)
A variable window's inner while-loop that advances left can still be O(n) overall because
  1. Left only moves forward, so those iterations sum to ≤ n across the whole run
  2. The while-loop is ignored by Big-O notation
  3. Right resets to 0 after every shrink

Amortization counts total left moves, not the worst single while. Resetting right would destroy the bound.

8Which pointer pattern fits

Sorted pair or container problems → opposite-end. Contiguous extremum under a constraint → sliding window (fixed if k is given, variable if length is the answer). Cycle or midpoint on a list → fast/slow. Unsorted pair sum with O(n) memory OK → hash map from the hashing topic instead.

If you catch yourself restarting a second index from 0 for every i, ask whether a monotonic pointer or window would let that index only move forward.

Pick from the pattern table: sorted pair sum → opposite-end; contiguous extremum under a constraint → sliding window; list cycle or midpoint → fast/slow; unsorted pair with O(n) memory → hash map.

Prompt → pattern
Prompt shapePattern
Sorted array, pair with target sumOpposite-end two pointers
Longest/shortest contiguous span with a propertyVariable sliding window
Best aggregate among spans of length kFixed window
Linked-list cycle or midpointFast / slow
Detecting a cycle in a singly linked list with O(1) extra memory points to
  1. Fast/slow pointers
  2. Opposite-end pointers on a sorted array
  3. A fixed window of length k = 2

Speed difference on a list finds cycles without a hash set of nodes. Opposite-end needs random access and sorted order; a fixed window is for contiguous aggregates.

Notes

  • Two Pointers: Use two indices moving toward each other (or in the same direction) to solve pair/partition problems in O(n) instead of O(n^2).
  • Opposite-End Pattern: On a sorted array, move a left and right pointer inward based on whether the current sum is too small or too large (e.g., pair-sum, container problems).
  • Sliding Window: Maintain a moving subarray/substring window, expanding the right pointer and shrinking the left to satisfy a constraint in a single pass.
  • Fixed vs Variable Window: Fixed-size windows solve 'best window of length k'; variable-size windows solve 'longest/shortest window meeting a condition.'
  • Fast/Slow Pointers: Two pointers at different speeds detect cycles or find a midpoint in one traversal.

Formulas

  • Two-pointer scan: O(n) time, O(1) extra space.
  • Sliding window: each element enters and leaves the window at most once, so total work is O(n).
  • Fixed window of size k: slide by removing the outgoing element and adding the incoming one in O(1) per step.
  • Sorting prerequisite: opposite-end two-pointer on unsorted data first needs an O(n \log n) sort.
  • Variable window: expand right, and while the constraint breaks, advance left - amortized O(n).

Exam traps & shortcuts

  • If the array is sorted and you need a pair/triplet meeting a sum condition, use opposite-end two pointers rather than nested loops.
  • For 'longest/shortest substring with condition X,' reach for a variable-size sliding window with a frequency map.
  • For a max/min sum of every length-k window, use a fixed sliding window and update in O(1) instead of recomputing.
  • Detecting a cycle or the middle of a linked list is a fast/slow (tortoise and hare) two-pointer problem.

Reference tables

Restated from the concepts — scan sheet only.

Complexity cheatsheet
PatternTimeExtra space
Two-pointer scan (pointers monotone)O(n)O(1)
Opposite-end after sortingO(n \log n)O(1)
Sliding window (fixed or variable)O(n)O(1) or O(k)
Fast/slow cycle or midpointO(n)O(1)

Identities the examples keep using.

Formula anchors
IdentityForm
Window lengthright − left + 1
Amortized window workeach index enters/leaves ≤ once → O(n)
Opposite-end movesum too small ⇒ left++; too big ⇒ right−−
Fast/slow stepslow +1, fast +2 per tick

Recap

Night-before pegs for two pointers and windows.

Core
Two monotone indices beat O(n^2) nested loops when each element is charged once.
Opposite-end
Sorted array: grow sum with left++, shrink with right−−. O(1) extra space.
Window
Expand right; shrink left while invalid; track best. Contiguous only.
Fixed vs variable
Fixed: length k given. Variable: length is the answer under a constraint.
Unique substring
"abcabcbb" → 3; set/map + shrink on duplicate.
Fast/slow
Cycle if they meet; midpoint when fast hits the end.
Cost
Inner while is fine — left never retreats, total still O(n).

Practise Two Pointers and Sliding Window

Reading is free and needs no account. Practice, mocks and progress live in the app.

  • 5 exam-style questions on this topic, with explanations
  • A 6-question practice set that ends the chapter
  • Timed mocks scored with the real marking scheme
  • Readiness tracked per topic, kept on your device
Continue with Google — freeNo card, no trial. Works offline once installed.