Two Pointers
Fundamental strategy for optimizing linear searches by coordinating two indices.
Two Pointers in Arrays and Strings
The Two Pointers technique is a cornerstone of algorithmic optimization. In the context of arrays and strings, it allows us to process data in a single pass ($O(n)$ time) with constant extra space O(1), often replacing brute-force nested loops that would otherwise take O(n^2).
By maintaining two indices that move independently based on logic, we can efficiently narrow down search spaces or modify data in-place.
1. Opposite Directions Pointers
The most classic application involves placing one pointer at the start (left) and one at the end (right). We then move them toward the center until they meet.
Case A: Search Space Reduction (Sorted Data)
When an array is sorted, we can use the relative values of the elements at both pointers to decide which one to move. If our current sum is too small, we move the left pointer to increase it; if too large, we move the right pointer to decrease it.
Example: Two Sum II (Sorted)
def two_sum(numbers, target):
l, r = 0, len(numbers) - 1
while l < r:
s = numbers[l] + numbers[r]
if s == target:
return [l + 1, r + 1]
elif s < target:
l += 1
else:
r -= 1Case B: Symmetry and In-place Modification
This is used for checking properties like palindromes or performing in-place swaps.
Common Problems:
- Valid Palindrome: Comparing characters from both ends.
- Reverse String: Swapping characters at
landruntil they meet. - Container With Most Water: Moving the pointer pointing to the shorter line to potentially find a taller one.
2. Two Pointers across Two Arrays
In some problems, you are given two separate arrays and need to merge or compare them. Here, each pointer tracks the current position in its respective array.
Case A: The "Merge" Logic
Used when you need to combine two sorted sequences into one.
Example: Merge Sorted Arrays
def merge(arr1, arr2):
p1, p2 = 0, 0
merged = []
while p1 < len(arr1) and p2 < len(arr2):
if arr1[p1] < arr2[p2]:
merged.append(arr1[p1])
p1 += 1
else:
merged.append(arr2[p2])
p2 += 1
# Append remaining elements
merged.extend(arr1[p1:])
merged.extend(arr2[p2:])
return merged3. Same Direction (Sequential) Pointers
While similar to a sliding window, this variation focuses on "reading" and "writing" pointers to modify an array in-place without using extra space.
Common Use Cases:
- Remove Duplicates: One pointer iterates through the array, while the other marks the position of the last unique element found.
- Move Zeroes: One pointer finds the next non-zero element, while the other keeps track of where the next non-zero should be placed.
Why this works
The magic of Two Pointers lies in its ability to exhaust possibilities without visiting them. By moving a pointer, you are effectively "discarding" a set of pairs or configurations that you know—mathematically—cannot be the solution, thus keeping the complexity linear.
When to apply this?
- The input is sorted (or can be sorted without hurting complexity).
- You need to find a pair/triple that meets a certain condition.
- You are asked to modify an array in-place (e.g., "without allocating extra space for another array").
- You are comparing two strings/arrays for subsequences or intersections.
Battle Ground
Mind map -
Use a two-pointer approach where the slow pointer tracks the position of the last unique element and the fast pointer iterates through the array. Starting from index 1, if the element at the fast pointer is different from the one before it, copy the fast pointer's value to the slow pointer's position and increment the slow pointer.
Pseudocode -
left = 1
for right starting from 1 to len(arr)-1:
if arr[right-1] < arr[right]:
arr[left] = arr[right]
left += 1
return left
Space and time complexity -
Space: O(1) - No additional memory used
Time: O(n) - for loop covers each element onceMind map -
We know the array is sorted, if we initialize two pointers each from both ends. If we sum up the elements from both the indexes, we will find out whether sum is greater, lesser or equal. If greater then we decrement the right pointer, if lesser then we increment the left pointer and if equal then we found the two elements.
Pseudocode -
left, right = 0, len(arr)-1
while left <= right:
if arr[left]+arr[right] < target:
left+=1
else if arr[left]+arr[right] > target:
right-=1
else:
return [left,right]
Space and time complexity -
Space : O(1) - no additional space used
Time : O(n) - while loop covers each element in the array once