Skip to content

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

Two pointers

Explanation

Two pointers are two index variables that move through an array in a planned way, so each step rules out many possibilities. Pointers at opposite ends of a SORTED array find pairs with a target sum and the container with most water in O(n). A slow and a fast pointer moving the same way filter an array in place. Sorting plus two pointers solves 3-sum in O(n²).

Why two pointers

A pointer here is just an index that marks a position. Two-pointer techniques move two indexes through an array so that each step rules out a whole group of answers. They often turn an O(n²) "try every pair" into one O(n) pass with O(1) extra space. There are two main shapes: pointers at opposite ends moving towards each other, and two pointers moving in the same direction at different speeds.

Opposite ends: a pair with a target sum

In a SORTED array, put left at the start and right at the end. If nums[left] + nums[right] is too small, the only way to grow the sum is to move left forward. If it is too big, move right back. If it equals the target, you are done.

Why no pair is missed: when the sum is too small, nums[left] plus the LARGEST value still available is too small, so nums[left] cannot be part of any answer and can be dropped. The same argument drops nums[right] when the sum is too big. Every step drops one item, so there are at most n steps: O(n) time and O(1) space. The hash map from Topic 1 is also O(n) time, but it needs O(n) space; two pointers need sorted input.

python
def pair_sum(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        s = nums[left] + nums[right]
        if s == target:
            return [left, right]
        if s < target:
            left += 1
        else:
            right -= 1
    return []

print(pair_sum([1, 3, 4, 6, 9], 10))   # Output: [0, 4]
print(pair_sum([1, 3, 4, 6, 9], 8))    # Output: []
For 8 the walk tries 1+9, 1+6, 3+6, 3+4 and stops when the pointers meet.

Container with most water

heights[i] is the height of a vertical line at position i. Two lines and the ground form a container that holds min(heights[l], heights[r]) × (r − l) units of water. Start with the widest container (l = 0, r = n − 1), record its area, then move the pointer at the SHORTER line inwards. Moving the taller one can never help: the width shrinks and the height is still capped by the same shorter line. O(n) time, O(1) space.

For heights [1, 8, 6, 2, 5, 4, 8, 3, 7] the best container uses the 8 at index 1 and the 7 at index 8: min(8, 7) × (8 − 1) = 49.

Same direction: slow and fast pointers

Here both pointers move left to right. The fast (read) pointer looks at every item; the slow (write) pointer marks where the next kept item goes. This is the write-pointer pattern from Module 2, and it filters an array in place in O(n) time and O(1) space. Problems usually ask you to return k, the number of kept items, and promise to look only at nums[:k].

python
def remove_value(nums, val):
    write = 0
    for read in range(len(nums)):
        if nums[read] != val:
            nums[write] = nums[read]
            write += 1
    return write            # nums[:write] holds the kept items

a = [3, 2, 2, 3, 4]
k = remove_value(a, 3)
print(k, a[:k])   # Output: 3 [2, 2, 4]

To remove duplicates from a SORTED array, only the test changes: equal values sit next to each other, so keep nums[read] when it differs from the last kept item, nums[write − 1].

Merging two sorted arrays

Put one pointer at the start of each sorted array. Repeatedly take the smaller of the two front items and move that pointer on. When one array runs out, copy the rest of the other. Each step moves one pointer, so merging arrays of sizes n and m is O(n + m). This merge is the heart of merge sort (Module 6) and of merging sorted linked lists (Module 4).

python
def merge(a, b):
    i = j = 0
    out = []
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            out.append(a[i])
            i += 1
        else:
            out.append(b[j])
            j += 1
    out.extend(a[i:])
    out.extend(b[j:])
    return out

print(merge([1, 4, 9], [2, 3, 10, 12]))   # Output: [1, 2, 3, 4, 9, 10, 12]

3-sum: sort, fix one, two-pointer the rest

To find all unique triplets that sum to 0, sort first (O(n log n)). Then, for each index i, look for pairs to the right of i that sum to −nums[i] with the opposite-ends walk. That is O(n) for each i, so O(n²) in total, much better than O(n³) for checking all triples.

Duplicates are the tricky part. Skip i when nums[i] == nums[i − 1], because the same first value would give the same triplets again. After finding a triplet, move left past every copy of its value and right past every copy of its value before continuing. You can also stop early when nums[i] > 0: every later number is at least as big, so no sum can reach 0.

Common mistakes

  • Using opposite-end pointers on an UNSORTED array. Fix: sort first (and remember that sorting changes the indexes), or use a hash map.
  • Writing while left <= right in pair sum, which can pair an item with itself. Fix: while left < right.
  • Moving the taller line in container with most water. Fix: always move the shorter one.
  • Forgetting to skip duplicates in 3-sum, so the same triplet appears twice. Fix: skip equal values for i, and after each match move past equal values on both sides.
  • Returning the list instead of the new length k when filtering in place, or reading items beyond k. Fix: return k and use only nums[:k].

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

Two pointers · 1/8

Two pointers

This lesson is about two pointers: two indexes that move in a planned way so that each step rules out many answers.

Prefer a video by a teacher? Find free videos on “two pointers technique container with most water 3sum remove duplicates sorted array” on YouTube ↗

In real life: Two products for a gift card

A shopping site can suggest "two items that use up your Rs 1,000 gift card exactly". Product prices are often already sorted for the price filter, so the two-pointer walk finds a matching pair in one pass with no extra memory: too cheap, try a pricier first item; too expensive, try a cheaper second item.

python
prices = [149, 299, 350, 499, 650, 850]
left, right = 0, len(prices) - 1
while left < right:
    total = prices[left] + prices[right]
    if total == 1000:
        print(prices[left], prices[right])
        break
    if total < 1000:
        left += 1
    else:
        right -= 1
Shows 350 650 after trying 149+850, 299+850 and 299+650.

Practice (0/4 done)

Task 1 · Code · Python

nums is sorted in non-decreasing order. Write pair_sum_sorted(nums, target) that returns [i, j] with i < j and nums[i] + nums[j] == target, using two pointers from opposite ends (O(n) time, O(1) extra space). If several pairs work, return the one with the smallest i, and for that i the largest j (this is exactly what the standard walk finds). If none, return []. Example: pair_sum_sorted([2, 7, 11, 15], 9) returns [0, 1].

Task 2 · Code · Python

nums is sorted in non-decreasing order. Write remove_duplicates(nums) that removes duplicates IN PLACE so the first k items are the distinct values in their original order, and returns k. What is left after position k does not matter. O(n) time, O(1) extra space; do not create a new list. Example: for [1, 1, 2] return 2, and nums[:2] is [1, 2].

Task 3 · Code · Python

heights[i] is the height of a vertical line at position i. Write max_area(heights) that returns the most water a container formed by two lines can hold: min(heights[l], heights[r]) * (r - l). Use two pointers in O(n) time. With fewer than 2 lines, return 0. Example: max_area([1, 8, 6, 2, 5, 4, 8, 3, 7]) returns 49.

Task 4 · Code · Python

Write three_sum(nums) that returns every UNIQUE triplet [a, b, c] of values at three different positions with a + b + c == 0. Write each triplet in increasing order (a <= b <= c) and return the list of triplets sorted. Sort nums and use two pointers: O(n²) time. Example: three_sum([-1, 0, 1, 2, -1, -4]) returns [[-1, -1, 2], [-1, 0, 1]].

Quick quiz

Question 1

1.nums is sorted and nums[left] + nums[right] is LESS than the target. What should you do?

Question 2

2.In container with most water, heights[left] = 3 and heights[right] = 8. Which pointer moves?

Question 3

3.What is the time of 3-sum with sorting and two pointers?

Question 4

4.Why can the opposite-ends walk drop nums[left] when the sum is too small?

Question 5

5.Merging sorted arrays of sizes n and m with two pointers costs…

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.

nums is sorted and nums[left] + nums[right] is LESS than the target. What should you do?

Answer: Move left one step right

Only a bigger left value can raise the sum; nums[left] cannot pair with anything left in the range.

In container with most water, heights[left] = 3 and heights[right] = 8. Which pointer moves?

Answer: left, because it is the shorter line

Every container that keeps the 3 is narrower and still capped at height 3, so the 3 can be dropped.

What is the time of 3-sum with sorting and two pointers?

Answer: O(n²)

Sorting is O(n log n), then an O(n) walk for each of n values of i gives O(n²).

Why can the opposite-ends walk drop nums[left] when the sum is too small?

Answer: Because even with the largest value still in range, nums[right], the sum is too small

Every other partner for nums[left] is smaller than nums[right], so every pair with nums[left] is too small.

Merging sorted arrays of sizes n and m with two pointers costs…

Answer: O(n + m)

Each step moves one pointer forward, so there are n + m steps in total.

n items and q range-sum queries. What is the total cost with a prefix array?Related

Answer: O(n + q)

O(n) to build once, then O(1) per query.

From Prefix sums and difference arrays

Why is reading nums[i] O(1) in an array?Related

Answer: Its position is start + i × size, one calculation

Items sit side by side and have the same size, so the address of item i is computed directly.

From Arrays: traversal and in-place updates

When merging two sorted lists, one list runs out. What do you do with the other?Related

Answer: Attach its remaining part in one step: tail.next = a or b

The remaining part is already sorted and larger than everything merged so far.

From Fast and slow pointers and merging lists

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 steps

  1. 2 more topics in “Hashing, two pointers and sliding window” before its exam: Sliding window: fixed size, Sliding window: variable size.
  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: Counting and grouping with hashing