Mastering Two Pointers: A Step-by-Step Guide to Solving Sequence Problems
Two Pointers is an algorithmic pattern used to solve problems by tracking two positions in a sequence at the same time.
A sequence can be:
- an array;
- a list;
- a string;
- a linked list.
Instead of checking every possible combination with nested loops, the Two Pointers pattern moves two pointers through the data using a clear rule.
This often improves a solution from O(n^2) to O(n).
What Kind of Problems Does Two Pointers Solve?
Two Pointers is useful when a problem asks you to work with two positions inside the same sequence.
It commonly solves problems like:
- finding two numbers that match a condition;
- checking if a string is a palindrome;
- reversing a string or list;
- moving values inside an array;
- removing duplicates from a sorted array;
- comparing values from both ends;
- working with sorted arrays;
- detecting cycles in linked lists.
The most important idea is:
Every time you move a pointer, you must know why that move is safe.
The Core Idea
A Two Pointers solution usually follows this structure:
- Create a left pointer.
- Create a right pointer.
- Compare the values at both pointers.
- Decide which pointer should move.
- Repeat until you find the answer or the pointers meet.
Example:
[2, 7, 11, 15]
L R
Here:
-
Lpoints to the beginning; -
Rpoints to the end.
When Should You Think About Two Pointers?
Ask yourself these questions:
- Is the input a sequence?
- Do I need to compare two values?
- Is the sequence sorted?
- Am I looking for a pair?
- Do I need to reverse something?
- Do I need to move values in-place?
- Can I discard part of the search space after each comparison?
If the answer is yes to some of these questions, Two Pointers may be useful.
Common Two Pointers Strategies
1. Opposite Direction Pointers
One pointer starts at the beginning.
The other pointer starts at the end.
[1, 2, 3, 4, 5]
L R
This is useful for:
- palindrome checks;
- reversing arrays;
- sorted Two Sum;
- container with most water.
2. Same Direction Pointers
Both pointers move from left to right.
[0, 1, 0, 3, 12]
R
W
Usually:
- one pointer reads values;
- the other pointer writes or tracks valid positions.
This is useful for:
- moving zeroes;
- removing duplicates;
- filtering values.
3. Fast and Slow Pointers
One pointer moves faster than the other.
slow -> moves 1 step
fast -> moves 2 steps
This is useful for:
- detecting cycles;
- finding the middle of a linked list;
- removing the nth node from the end.
Step-by-Step Method
Use this process when solving a Two Pointers problem.
Step 1: Understand the Input
Ask:
What is the sequence?
Example:
numbers = [2, 7, 11, 15]
The sequence is a list of numbers.
Step 2: Decide Where the Pointers Start
For a sorted array pair problem:
left = 0
right = length - 1
Example:
[2, 7, 11, 15]
L R
Step 3: Write the Movement Rules
Before coding, write the rules in plain English.
For Two Sum II:
If sum == target, return the answer.
If sum < target, move the left pointer to the right.
If sum > target, move the right pointer to the left.
Step 4: Trace the Example by Hand
Example:
numbers = [2, 7, 11, 15]
target = 9
Trace:
left = 0, right = 3
2 + 15 = 17
17 is greater than 9
Move right left
left = 0, right = 2
2 + 11 = 13
13 is greater than 9
Move right left
left = 0, right = 1
2 + 7 = 9
Found the answer
Answer:
[1, 2]
Step 5: Write the Code
Example in Elixir:
defmodule Solution do
@spec two_sum([integer()], integer()) :: [integer()]
def two_sum(numbers, target) do
values = List.to_tuple(numbers)
search(values, target, 0, tuple_size(values) - 1)
end
defp search(values, target, left, right) do
sum = elem(values, left) + elem(values, right)
cond do
sum == target ->
[left + 1, right + 1]
sum < target ->
search(values, target, left + 1, right)
sum > target ->
search(values, target, left, right - 1)
end
end
end
Example: Valid Palindrome
Problem:
Given a string, return true if it reads the same forward and backward.
Example:
"level"
Expected output:
true
Pointer idea:
l e v e l
L R
Rules:
If characters are different, return false.
If characters are equal, move both pointers inward.
If pointers meet or cross, return true.
Elixir solution:
defmodule Solution do
@spec palindrome?(String.t()) :: boolean()
def palindrome?(s) do
chars = s |> String.graphemes() |> List.to_tuple()
check(chars, 0, tuple_size(chars) - 1)
end
defp check(_chars, left, right) when left >= right, do: true
defp check(chars, left, right) do
if elem(chars, left) == elem(chars, right) do
check(chars, left + 1, right - 1)
else
false
end
end
end
Trace:
"level"
left = 0, right = 4
l == l
left = 1, right = 3
e == e
left = 2, right = 2
stop
true
How to Practice Two Pointers
Follow this routine:
- Read the problem.
- Identify the sequence.
- Choose where each pointer starts.
- Write the movement rules in English.
- Trace one example manually.
- Write the code.
- Test with small inputs.
- Explain why each pointer movement is safe.
Do not jump straight to code.
The pointer movement is the most important part.
Exercises
Exercise 1: Valid Palindrome
Input:
"racecar"
Expected output:
true
Practice:
- Start one pointer at the beginning.
- Start one pointer at the end.
- Compare both characters.
- Move inward when they match.
Extra tests:
"level" -> true
"hello" -> false
"a" -> true
Exercise 2: Reverse List
Input:
[1, 2, 3, 4, 5]
Expected output:
[5, 4, 3, 2, 1]
Practice:
- Start one pointer at the beginning.
- Start one pointer at the end.
- Swap both values.
- Move both pointers inward.
Extra tests:
[] -> []
[1] -> [1]
[1, 2] -> [2, 1]
Exercise 3: Two Sum II
Input:
numbers = [2, 7, 11, 15]
target = 9
Expected output:
[1, 2]
Practice:
- Use a sorted array.
- Move the left pointer when the sum is too small.
- Move the right pointer when the sum is too large.
Extra tests:
[1, 2, 3, 4, 6], target = 6 -> [2, 4]
[2, 3, 4], target = 6 -> [1, 3]
[-1, 0], target = -1 -> [1, 2]
Exercise 4: Move Zeroes
Input:
[0, 1, 0, 3, 12]
Expected output:
[1, 3, 12, 0, 0]
Practice:
- Use one pointer to read.
- Use another pointer to track where the next non-zero value should go.
- Keep the original order of non-zero values.
Extra tests:
[0] -> [0]
[1, 0] -> [1, 0]
[0, 0, 1] -> [1, 0, 0]
Exercise 5: Container With Most Water
Input:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
Expected output:
49
Practice:
- Start with the widest container.
- Calculate the current area.
- Move the pointer with the smaller height.
- Keep the best area found.
Key idea:
The shorter side limits the area.
Move the shorter side because it is the only side that can improve the result.
Final Checklist
Before using Two Pointers, answer:
- Where does the left pointer start?
- Where does the right pointer start?
- What does each pointer represent?
- What condition moves the left pointer?
- What condition moves the right pointer?
- When does the algorithm stop?
- Why is each pointer movement safe?
- What is the time complexity?
- What is the space complexity?
If you can answer these questions, you understand the pattern.
This article was originally published on AlchemistDrops.
Top comments (0)