All topics
19

Closing in from both ends

Tier 1Free

Where it runs

1. The problem

Checking every pair of items costs O(n2), and the Big-O lesson showed how fast that grows. When the data is sorted, or you only care about items next to each other, two moving markers do in one pass what nested loops need n2 steps for.

You have a price list, already sorted from cheap to expensive, and a gift card worth an exact amount. You want two items whose prices add up to exactly that amount, so you spend the card to the last cent.

The first idea is to try every pair: item 1 with item 2, item 1 with item 3, and so on. For a short list that is fine. For a list of a million prices it is a million times a million pairs: the O(n2) trap from lesson 09.

The list is sorted, though, and the order lets you throw away whole parts of the list without checking them. That gives the answer in one pass.

2. In plain words

Take the first test: the prices [1, 3, 4, 6, 8] and the target 10. Put one marker on the cheapest price, 1, and the other on the most expensive, 8. Together they make 9, which is too little.

Which side do you move? The 1 fell short even together with the 8, the largest price there is. With any cheaper price it would fall short by more. So the 1 is in no answer, and you can drop it: move the left marker to 3. Now 3 + 8 = 11, too much. By the same reasoning the other way round, the 8 is too big even with the smallest price left, so drop it: the right marker moves to 6. Then 3 + 6 = 9, too little again, so the left marker moves to 4. And 4 + 6 = 10. Found it.

Each move dropped one price together with every pair it could form, and nothing was checked twice. Four sums were enough instead of all ten pairs.

3. The idea

Put marker lo on the cheapest item and marker hi on the most expensive one. Add up the two prices they point at.

  • The sum equals the target: the pair is found. Done.
  • The sum is too small: the only way to make it bigger is to take a pricier item instead of the cheap one. Move lo one position to the right.
  • The sum is too big: move hi one position to the left so the sum gets smaller.

Each step is safe because the list is sorted. Say the sum is too small. Then nums[lo] falls short of the target even together with the largest remaining price, nums[hi]. With every other, cheaper price it falls short by more. So nums[lo] is in no valid pair, and you can drop it. Stop when the markers meet.

1 3 4 6 8 lo → ← hi move the side that fixes the sum

Trace it on [1, 3, 4, 6, 8] with target 10. lo points at 1 and hi at 8: the sum is 9, too small, move lo. lo at 3, hi at 8: the sum is 11, too big, move hi. lo at 3, hi at 6: the sum is 9, too small, move lo. lo at 4, hi at 6: the sum is 10, found. The answer is positions 2 and 3.

4. What it costs

Time is O(n): each marker only moves inward, so together they take at most n steps. Memory is O(1): two variables with indices, and nothing grows with the input. If the list has to be sorted first, add O(n log n) for the sort.

Compare it with the hash approach from lesson 18. It also finds a pair in O(n) time, but it spends O(n) memory on a set. On data that is already sorted, two pointers give the same speed and need no extra memory.

5. Where this shows up

Payment matching: in a ledger sorted by amount, find the two original charges that together make up the amount of a refund. An order book (a list of orders sorted by price) matches a buy order with a sell order. Antifraud systems (fraud detection) and deduplication pipelines look through sorted records for a pair that differs by no more than an allowed amount. Wherever money or ordered records are matched, the task looks the same: sorted input and a search for a pair. Two pointers solve it.

Pair with sum 14 in a sorted array
twoPointers([1, 2, 4, 6, 8, 11, 15], 14)
arr (sorted)
1
2
4
6
8
11
15

lo

hi
target = 14sum = -return = ?

Find two numbers that add up to 14. The array is sorted, so lo starts at the smallest number and hi at the biggest.

Speed race
Nested loops and two pointers looking for a pair with a given sum in the same 40 sorted numbers

Bars: the sorted numbers. Exactly one pair of them adds up to the target.

One operation is one pair checked. Every frame, each lane does the same number of operations, so the slow one falls behind.

Nested loops O(n²)
0 operations
Two pointers O(n)
0 operations

 

And with more numbers

Operations for n numbers, and roughly how long they take at 100 million simple operations a second. An estimate from the same count, not a measurement.

n = 1,000
Nested loops455K · 4.5 ms
Two pointers850 · under 1 ms
n = 100,000
Nested loops4.6B · 46 s
Two pointers85K · under 1 ms
n = 10,000,000
Nested loops45.5T · 5.3 days
Two pointers8.5M · 85 ms

Recognise it

Two pointers (opposite ends)

Say these words and it is this pattern

  • sorted array (or you are allowed to sort)
  • find a pair/triple with a target sum
  • squeeze from both ends (container, area)
  • palindrome check

Looks similar, isn't

  • Sliding window (variable)

    A window keeps a live span and both ends move forward. These pointers start apart and close in.

  • Hash set / map

    Hashing finds a complement in unsorted data, at the cost of O(n) memory. Two pointers need order but no memory.

  • Two pointers (same direction)

    There both indices move forward, one reading and one writing. Here they approach from opposite ends.

  • Binary search (array)

    Binary search hunts one fixed target and halves the range. Here the pair being compared changes every step.

n up to 1e6 on sorted input gives O(n) time. Each pointer moves at most n steps, and space is O(1).

On LeetCode

Problems on a sorted array, or a string read from both ends, where you need a pair with a given sum, a palindrome check or the best pair. Two pointers moving toward each other replace checking every pair.

Your task (Tier 1)

Write the function twoSumSorted(nums, target).

Find two numbers that add up to target. The list is sorted from low to high, so you can move in from both ends.

  • Input: nums, a list of numbers sorted from low to high, and target, a number. Example: [2, 7, 11, 15], 9.
  • Return: the two positions [lo, hi] of a pair that adds up to target. If no pair works, return null (None in Python).

Steps:

  1. Put a marker lo at the start (position 0) and a marker hi at the last position.
  2. While lo < hi, compute nums[lo] + nums[hi]. If it equals target, return [lo, hi]. If the sum is smaller, move lo one step right. If it is bigger, move hi one step left.
  3. If the markers meet and no pair was found, return null.

Done when all tests pass:

  • [1, 3, 4, 6, 8], target 10 -> [2, 3]
  • [2, 7, 11, 15], target 9 -> [0, 1]
  • [1, 2, 3, 4, 4], target 8 -> [3, 4] (repeated values)
  • [5], target 5 -> null (one item, no pair)
  • [], target 0 -> null (empty list, must not crash)
  • [1, 2, 3], target 100 -> null (no pair reaches it)

Practice

twoSumSorted

twoSumSorted(nums, target) -> [i, j] (0-based indices, i < j) or null

export function twoSumSorted(nums, target) {
  // Your code here. The task is described in the box above the editor.
}

Running as a guest

The tests run right here, no account needed. Your code is kept in this browser only. A free account keeps it on every device, marks the step done and shows where you are in the course.

Hint ladder

Stuck? Reveal one rung at a time, but try it yourself first.

    We use necessary cookies to run the site (sign-in and language). If you accept, we also load Google Analytics to see which pages are used, and Google reCAPTCHA to keep spam off the contact and bug-report forms. Privacy policy