Two Pointers
The two-pointer technique uses two indices that traverse a data structure—typically a sorted array—either moving toward each other from opposite ends or advancing in the same direction. By narrowing the search space with each step, two pointers reduce brute-force O(n²) solutions down to a single linear pass, making them indispensable for problems involving pair sums, partitioning, deduplication, and subarray manipulation.
How Two Pointers Works
The core idea is simple: instead of checking every possible pair or subarray with nested loops, maintain two index variables that move strategically based on a condition. There are two main variants:
Opposite-direction pointers (converging): One pointer starts at the beginning and the other at the end. They move toward each other until they meet. This works especially well on sorted arrays.
- Pair sum in a sorted array: Given a sorted array and a target sum, place the left pointer at index 0 and the right pointer at the last index. If the sum of the two elements equals the target, you found the pair. If the sum is too small, move the left pointer right to increase it. If the sum is too large, move the right pointer left to decrease it. Each step eliminates an entire row or column of the search space.
- Container with most water: Place pointers at both ends. The area is determined by the shorter line times the distance between them. Move the pointer at the shorter line inward, because moving the taller one can never increase the area. This greedy narrowing guarantees you find the maximum.
Same-direction pointers (fast and slow): Both pointers start at the beginning. A "fast" pointer advances on every iteration while a "slow" pointer only advances when a condition is met. This pattern is ideal for in-place modifications.
- Removing duplicates from a sorted array: The slow pointer marks the position of the last unique element. The fast pointer scans ahead. When the fast pointer finds a value different from the slow pointer's value, increment the slow pointer and copy the new value there. After one pass, everything up to the slow pointer is the deduplicated result.
- Partitioning: Similar to the partition step in quicksort, a slow pointer tracks where the next qualifying element should go, while a fast pointer scans the array for elements that meet the criterion.
Step-by-step example — Pair Sum (target = 16):
- Step 1: Array = [1, 3, 5, 7, 8, 9, 11, 15]. Left = 0 (value 1), Right = 7 (value 15). Sum = 16. Match found!
- If the target were 12: Sum of 1 + 15 = 16 > 12, so move Right left. Sum of 1 + 11 = 12. Match found.
- If the target were 20: Sum of 1 + 15 = 16 < 20, so move Left right. Sum of 3 + 15 = 18 < 20, move Left right. Sum of 5 + 15 = 20. Match found.
Key insight: Two pointers work because each pointer movement provably eliminates candidates that cannot be part of the optimal solution. This requires a monotonic relationship—typically provided by sorting—so that moving one pointer in a direction consistently increases or decreases the quantity of interest.
Interactive Visualization
Watch the two-pointer technique find a pair that sums to the target value in a sorted array. The left pointer advances from the start and the right pointer retreats from the end. When they meet or a match is found, the search is complete.
Code Implementation
The classic two-pointer pair-sum solution on a sorted array. The left pointer starts at the beginning and the right pointer at the end. Depending on the current sum relative to the target, one pointer moves inward.
def two_pointer_pair_sum(arr, target):
"""Find a pair in sorted array that sums to target.
Args:
arr: A sorted list of integers.
target: The desired pair sum.
Returns:
A tuple (i, j) of indices if found, or (-1, -1) if no pair exists.
"""
left = 0
right = len(arr) - 1
while left < right:
current_sum = arr[left] + arr[right]
if current_sum == target:
return (left, right)
elif current_sum < target:
left += 1 # Need a larger sum
else:
right -= 1 # Need a smaller sum
return (-1, -1) # No pair found
def remove_duplicates(arr):
"""Remove duplicates from a sorted array in-place.
Returns:
The length of the array after removing duplicates.
"""
if not arr:
return 0
slow = 0
for fast in range(1, len(arr)):
if arr[fast] != arr[slow]:
slow += 1
arr[slow] = arr[fast]
return slow + 1
# Example usage
arr = [1, 3, 5, 7, 8, 9, 11, 15]
target = 16
result = two_pointer_pair_sum(arr, target)
print(f"Pair found at indices {result}: "
f"{arr[result[0]]} + {arr[result[1]]} = {target}")
# Output: Pair found at indices (0, 7): 1 + 15 = 16
public class TwoPointers {
/**
* Find a pair in a sorted array that sums to target.
* Returns an int[] with two indices, or {-1, -1} if not found.
*/
public static int[] pairSum(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
return new int[]{left, right};
} else if (sum < target) {
left++; // Need a larger sum
} else {
right--; // Need a smaller sum
}
}
return new int[]{-1, -1}; // No pair found
}
/**
* Remove duplicates from a sorted array in-place.
* Returns the new length.
*/
public static int removeDuplicates(int[] arr) {
if (arr.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < arr.length; fast++) {
if (arr[fast] != arr[slow]) {
slow++;
arr[slow] = arr[fast];
}
}
return slow + 1;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 8, 9, 11, 15};
int target = 16;
int[] result = pairSum(arr, target);
System.out.printf("Pair found at indices [%d, %d]: %d + %d = %d%n",
result[0], result[1],
arr[result[0]], arr[result[1]], target);
// Output: Pair found at indices [0, 7]: 1 + 15 = 16
}
}
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
/**
* Find a pair in a sorted array that sums to target.
* Returns a pair of indices, or {-1, -1} if not found.
*/
pair<int, int> pairSum(const vector<int>& arr, int target) {
int left = 0;
int right = static_cast<int>(arr.size()) - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
return {left, right};
} else if (sum < target) {
left++; // Need a larger sum
} else {
right--; // Need a smaller sum
}
}
return {-1, -1}; // No pair found
}
/**
* Remove duplicates from a sorted array in-place.
* Returns the new length.
*/
int removeDuplicates(vector<int>& arr) {
if (arr.empty()) return 0;
int slow = 0;
for (int fast = 1; fast < static_cast<int>(arr.size()); fast++) {
if (arr[fast] != arr[slow]) {
slow++;
arr[slow] = arr[fast];
}
}
return slow + 1;
}
int main() {
vector<int> arr = {1, 3, 5, 7, 8, 9, 11, 15};
int target = 16;
auto [i, j] = pairSum(arr, target);
cout << "Pair found at indices [" << i << ", " << j
<< "]: " << arr[i] << " + " << arr[j]
<< " = " << target << endl;
// Output: Pair found at indices [0, 7]: 1 + 15 = 16
return 0;
}
Complexity Analysis
The two-pointer technique eliminates the need for nested iteration. Each pointer moves at most n times and never backtracks, so the total work is linear. No auxiliary data structures are required beyond the two index variables.
| Metric | Complexity | Description |
|---|---|---|
| Time | O(n) | Each pointer traverses at most n elements; combined at most 2n steps |
| Space | O(1) | Only two index variables are used regardless of input size |
Note: If the input array is not already sorted, an O(n log n) sorting step is required first. In that case the overall time complexity becomes O(n log n), dominated by the sort. The two-pointer scan itself remains O(n). For problems like "Container With Most Water," the array is not sorted by value but the two-pointer approach still works because the decision to move a pointer is based on height comparisons, not value ordering.
Quiz
Test your understanding of the two-pointer technique with these questions.
Q1. Why does the two-pointer pair-sum technique require the array to be sorted?
Q2. Given sorted array [2, 4, 6, 8, 10, 12] and target = 14, what is the first pair of values compared?
Q3. In the "remove duplicates from sorted array" problem, what role does the slow pointer play?
Q4. What is the time complexity of the two-pointer pair-sum technique on a sorted array of n elements?
Q5. In the "Container With Most Water" problem, why do we move the pointer at the shorter line inward?