Python practice

Python two pointers practice questions

5 problems, ordered from easiest. They run in your browser, and 3 are free.

Start with Merge Two Sorted Lists
On this page13 sections
  1. The idea in a minute
  2. Why check every pair when you can be smarter
  3. Watch the pointers close in
  4. Why moving a pointer is safe
  5. The loop condition and the edges
  6. Another use: reverse in place
  7. Another use: is it a palindrome
  8. A different flavour: both pointers move the same way
  9. Merging two sorted lists
  10. When two pointers is the wrong tool
  11. Questions people ask
  12. Now try it yourself
  13. More Python topics

The idea in a minute

On a sorted list you do not have to check every pair. Put a finger at each end, look at the sum, and move the one that helps. You never go back, so it takes one pass.

The page shows why moving a pointer is safe, then uses the idea for pairs, reversing, palindromes, removing duplicates and merging sorted lists.

Why check every pair when you can be smarter

You have a sorted list and want two numbers that add up to a target. The first idea is a loop inside a loop: try every pair. With 1,000 numbers that is about half a million pairs. But the list is sorted, and that is a gift. It lets you rule out many pairs with a single look.

Put one finger on the smallest number and one on the biggest. Add them. Too small? The only way to get bigger is to move the left finger to a larger number. Too big? Move the right finger to a smaller number. You never go back. That is two pointers.

Watch the pointers close in

Target 13 in a sorted list

Step 1 / 7
1a = [1, 3, 4, 6, 8, 9]
2t = 13
3l, r = 0, len(a) - 1
4while l < r:
5 s = a[l] + a[r]
6 if s == t:
7 print(l, r, a[l], a[r])
8 break
9 l, r = (l + 1, r) if s < t else (l, r - 1)
Pointers and sum
134689
lr
Line 3
One pointer at each end of the sorted list. The target is 13.
PythonFind a pair in a sorted list
a = [1, 3, 4, 6, 8, 9]
t = 13
l, r = 0, len(a) - 1
while l < r:
    s = a[l] + a[r]
    if s == t:
        print(l, r, a[l], a[r])
        break
    l, r = (l + 1, r) if s < t else (l, r - 1)
2 5 4 9

Three steps, not fifteen. The line at the bottom, l, r = (l + 1, r) if s < t else (l, r - 1), is the whole strategy: too small moves the left pointer, too big moves the right one.

Why moving a pointer is safe

This is the part to understand, because it is the proof that you did not skip the answer. Say the sum of a[l] and a[r] is too small. Then a[l] paired with anything at or left of r gives something even smaller (the list is sorted, so everything left of r is smaller). That means a[l] can never be part of the answer, and it is safe to drop it.

RememberSay the rule out loud

Before you code a two pointers question, say: 'if the sum is too small I move ___, if it is too big I move ___.' If you cannot say it, two pointers is not the right tool, or the list needs sorting first.

The loop condition and the edges

PythonA reusable function and its edge cases
def pair_with_sum(a, t):
    l, r = 0, len(a) - 1
    while l < r:
        s = a[l] + a[r]
        if s == t:
            return (a[l], a[r])
        if s < t:
            l += 1
        else:
            r -= 1
    return None

print(pair_with_sum([1, 3, 4, 6, 8, 9], 13))
print(pair_with_sum([1, 3, 4, 6, 8, 9], 100))
print(pair_with_sum([5], 10))
print(pair_with_sum([], 10))
(4, 9)
None
None
None

l < r (not l <= r) stops the pointers from meeting on the same item, because a number cannot be paired with itself. It also handles one item and an empty list for free: the loop just never starts.

Another use: reverse in place

Two pointers does not have to hunt for a target. Swap the ends and walk inward, and you have reversed the list without making a copy.

PythonReverse a list in place
a = [1, 2, 3, 4, 5]
l, r = 0, len(a) - 1
while l < r:
    a[l], a[r] = a[r], a[l]
    l += 1
    r -= 1
print(a)
[5, 4, 3, 2, 1]

Another use: is it a palindrome

PythonCompare from both ends
def is_palindrome(s):
    l, r = 0, len(s) - 1
    while l < r:
        if s[l] != s[r]:
            return False
        l += 1
        r -= 1
    return True

print(is_palindrome("level"))
print(is_palindrome("lever"))
True
False

Same shape, different rule: if the two ends differ, stop with False. If the pointers meet, every pair matched.

A different flavour: both pointers move the same way

So far the pointers came from opposite ends. The other common setup has both starting at the left. A slow pointer marks where the next good item should go, and a fast pointer scans ahead. Removing duplicates from a sorted list is the classic case.

PythonRemove duplicates from a sorted list
a = [1, 1, 2, 2, 2, 3, 4, 4]
slow = 0
for fast in range(1, len(a)):
    if a[fast] != a[slow]:
        slow += 1
        a[slow] = a[fast]
unique = a[:slow + 1]
print(unique)
[1, 2, 3, 4]

fast looks at every item. Whenever it finds something different from the last kept item, slow moves one step and keeps it. Everything at or before slow is the cleaned list, no extra list needed during the scan.

Merging two sorted lists

PythonOne pointer per list
a = [1, 4, 7]
b = [2, 3, 9]
i = j = 0
merged = []
while i < len(a) and j < len(b):
    if a[i] <= b[j]:
        merged.append(a[i])
        i += 1
    else:
        merged.append(b[j])
        j += 1
merged += a[i:] + b[j:]
print(merged)
[1, 2, 3, 4, 7, 9]

Take the smaller of the two front items, move that pointer. When one list runs out, add whatever is left of the other. This is the merge step of merge sort.

When two pointers is the wrong tool

  • The list is not sorted and you cannot sort it (the positions matter). A dictionary is the usual answer, see the hashing topic.
  • You need every pair, not just one. Two pointers finds one answer per pass.
  • The condition is not monotonic: moving a pointer does not reliably make the sum bigger or smaller.

Questions people ask

Two pointers or hash map for two sum?

Sorted input and no extra memory wanted: two pointers. Unsorted input and you need the original positions: hash map.

Why does it take one pass?

Each step moves a pointer by one and never back, so the two of them together cover at most n steps.

Is it the same as a sliding window?

Related. A sliding window cares about everything between the two pointers (a running total of that block). Two pointers from opposite ends only care about the values at the two pointers.

Keep this checklist handy

  • The list usually needs to be sorted first, or the moves have no meaning.
  • Use l < r in the loop so the pointers never cross.
  • Move exactly one pointer per step, or you can skip the answer.

Now try it yourself

5 two pointers problems. They run right here in your browser, and you can start anywhere.

Warm up

One idea at a time. Do these in order.

  1. 1Merge Two Sorted ListsFree
  2. 23SumFree
  3. 3Container With Most WaterFree

Getting comfortable

Two ideas together, or a trap to spot.

  1. 4Trapping Rain Water
  2. 5Valid Palindrome

Want a guided order across every topic? Open the practice sheets or the Python hub.

More Python topics