Two Pointers Pattern
Why Two Pointers Matter
Two pointers technique solves problems with O(n) time and O(1) space—eliminating nested loops:
- Array/string problems: Find pairs, reverse, partition
- Linked lists: Detect cycles, find middle
- Sliding window: Often implemented with two pointers
- Palindrome detection: Front and back pointers
Real-world impact: Finding all pairs with a given sum in an array:
- Brute force: O(n²) with two nested loops
- Two pointers: O(n) after sorting—1000x faster for 10,000 elements
Core Concepts
Two Pointer Patterns
1. Opposite Direction Pointers
Start at both ends, move towards each other:
int left = 0, right = arr.length - 1;
while (left < right) {
// Process arr[left] and arr[right]
left++;
right--;
}
Use cases:
- Two sum (sorted array)
- Palindrome checking
- Container with most water
- Reverse array/string
2. Same Direction Pointers (Fast/Slow)
Both start at beginning, fast moves ahead:
int slow = 0, fast = 0;
while (fast < arr.length) {
// Process
fast++;
if (condition) slow++;
}
Use cases:
- Remove duplicates
- Remove element
- Cycle detection in linked lists
- Find middle element
3. Fixed Pointers
Maintain fixed distance between pointers:
int left = 0, right = k; // k is window size or fixed distance
while (right < arr.length) {
// Compare arr[left] and arr[right]
left++;
right++;
}
Use cases:
- Find subarrays with property
- Fixed-size sliding window
- Compare elements k distance apart
Deep Dive
Two Sum II - Sorted Array
Given sorted array, find two numbers that sum to target:
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1}; // 1-indexed
} else if (sum < target) {
left++; // Need larger sum
} else {
right--; // Need smaller sum
}
}
return new int[]{-1, -1}; // Not found
}
Why it works: Array is sorted, so:
- If sum < target: need larger elements → move left pointer right
- If sum > target: need smaller elements → move right pointer left
Valid Palindrome
Check if string is palindrome (ignoring non-alphanumeric):
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
// Skip non-alphanumeric
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
left++;
}
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
right--;
}
// Compare characters
char c1 = Character.toLowerCase(s.charAt(left));
char c2 = Character.toLowerCase(s.charAt(right));
if (c1 != c2) return false;
left++;
right--;
}
return true;
}
Container With Most Water
Find max area formed by two vertical lines:
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int maxArea = 0;
while (left < right) {
int width = right - left;
int containerHeight = Math.min(height[left], height[right]);
int area = width * containerHeight;
maxArea = Math.max(maxArea, area);
// Move shorter line inward
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
Key insight: Moving the longer line inward cannot increase area (height limited by shorter line, width decreases). Always move shorter line.
Fast/Slow Pointer
Remove Duplicates from Sorted Array
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0; // Position to place next unique element
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1; // Length of unique portion
}
Common Pitfalls
❌ Not handling edge cases
public boolean badPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) { // NPE if s is empty
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
✅ Handle empty input
public boolean goodPalindrome(String s) {
if (s == null || s.isEmpty()) return true;
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
❌ Infinite loop with wrong pointer update
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) return true;
if (sum < target) left = left; // BUG: Not moving!
else right = right; // BUG: Not moving!
}
✅ Always update pointers
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) return true;
if (sum < target) left++; // Move left forward
else right--; // Move right backward
}
❌ Overflow with product
int area = height[left] * height[right] * (right - left);
// Can overflow for large values!
✅ Use long or min
int width = right - left;
int containerHeight = Math.min(height[left], height[right]);
int area = width * containerHeight; // Less likely to overflow
// Or use long for safety
Advanced Patterns
Three Sum
Find all unique triplets summing to zero:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
// Skip duplicates
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// Skip duplicates
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
Partition Array (Dutch National Flag)
public void sortColors(int[] nums) {
int low = 0, mid = 0, high = nums.length - 1;
while (mid <= high) {
if (nums[mid] == 0) {
swap(nums, low++, mid++);
} else if (nums[mid] == 1) {
mid++;
} else { // nums[mid] == 2
swap(nums, mid, high--);
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
Three pointers:
low: Boundary for 0s (elements before low are 0)mid: Current elementhigh: Boundary for 2s (elements after high are 2)
Practical Applications
Merge Two Sorted Arrays
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1; // Pointer for nums1
int p2 = n - 1; // Pointer for nums2
int p = m + n - 1; // Pointer for merged array
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--];
} else {
nums1[p--] = nums2[p2--];
}
}
// Copy remaining elements from nums2
while (p2 >= 0) {
nums1[p--] = nums2[p2--];
}
}
Merge from end to avoid overwriting nums1 elements
Move Zeroes
public void moveZeroes(int[] nums) {
int slow = 0; // Position for next non-zero
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != 0) {
nums[slow] = nums[fast];
if (slow != fast) {
nums[fast] = 0;
}
slow++;
}
}
}
Squares of Sorted Array
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] result = new int[n];
int left = 0, right = n - 1;
int pos = n - 1; // Fill from end
while (left <= right) {
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
if (leftSquare > rightSquare) {
result[pos--] = leftSquare;
left++;
} else {
result[pos--] = rightSquare;
right--;
}
}
return result;
}
Largest square comes from either most negative or most positive number
Interview Questions
Q1: Two Sum II - Input Array Is Sorted (Easy)
Problem: Find two numbers summing to target in sorted array.
Approach: Opposite direction pointers
Complexity: O(n) time, O(1) space
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[]{-1, -1};
}
Q2: Valid Palindrome (Easy)
Problem: Check if string is palindrome (ignoring case and non-alphanumeric).
Approach: Two pointers from ends
Complexity: O(n) time, O(1) space
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
left++;
}
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
right--;
}
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}
Q3: Remove Element (Easy)
Problem: Remove all instances of value in-place.
Approach: Slow/fast pointers
Complexity: O(n) time, O(1) space
public int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
Q4: Max Number of K-Sum Pairs (Medium)
Problem: Find max number of pairs summing to k.
Approach: Sort + two pointers
Complexity: O(n log n) time, O(1) space
public int maxOperations(int[] nums, int k) {
Arrays.sort(nums);
int left = 0, right = nums.length - 1;
int operations = 0;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == k) {
operations++;
left++;
right--;
} else if (sum < k) {
left++;
} else {
right--;
}
}
return operations;
}
Q5: 3Sum (Medium)
Problem: Find all unique triplets summing to zero.
Approach: Sort + two pointers for each element
Complexity: O(n²) time, O(1) space (excluding output)
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
Q6: Container With Most Water (Medium)
Problem: Find max area container.
Approach: Opposite pointers, move shorter line
Complexity: O(n) time, O(1) space
public int maxArea(int[] height) {
int left = 0, right = height.length - 1;
int maxArea = 0;
while (left < right) {
int width = right - left;
int containerHeight = Math.min(height[left], height[right]);
maxArea = Math.max(maxArea, width * containerHeight);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
Q7: Trapping Rain Water (Hard)
Problem: Calculate trapped water between bars.
Approach: Two pointers tracking max heights
Complexity: O(n) time, O(1) space
public int trap(int[] height) {
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
int water = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
water += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
water += rightMax - height[right];
}
right--;
}
}
return water;
}
Further Reading
- Sliding Window: Often implemented with two pointers
- Binary Search: Another divide-and-conquer technique
- Linked Lists: Fast/slow pointer for cycle detection
- LeetCode: Two Pointer problems