Two pointers is one of the most versatile DSA patterns. Here's every variant — opposite ends, fast-slow, and multi-array — with Java solutions and when to reach for each.
Two pointers shows up in at least one problem every LeetCode contest. Once you recognize the three variants, you'll solve these in under 10 minutes. Let me break each one down.
Start one pointer at index 0 and one at n-1. Move them toward each other based on a condition. Classic use: two-sum on a sorted array, container with most water.
1// Two Sum II — sorted array, O(n) time O(1) space
2public int[] twoSum(int[] numbers, int target) {
3 int left = 0, right = numbers.length - 1;
4 while (left < right) {
5 int sum = numbers[left] + numbers[right];
6 if (sum == target) return new int[]{left + 1, right + 1};
7 if (sum < target) left++;
8 else right--;
9 }
10 return new int[]{};
11}One pointer moves 1 step, one moves 2 steps. Used to detect cycles in linked lists and find the middle node. Floyd's algorithm is the canonical example.
1public boolean hasCycle(ListNode head) {
2 ListNode slow = head, fast = head;
3 while (fast != null && fast.next != null) {
4 slow = slow.next;
5 fast = fast.next.next;
6 if (slow == fast) return true;
7 }
8 return false;
9}
10
11// Find middle of linked list
12public ListNode middleNode(ListNode head) {
13 ListNode slow = head, fast = head;
14 while (fast != null && fast.next != null) {
15 slow = slow.next;
16 fast = fast.next.next;
17 }
18 return slow; // slow is at middle when fast reaches end
19}Both pointers start at 0. The slow pointer tracks the position of the next valid element; the fast pointer scans ahead. Used for in-place array modifications.
1public int removeDuplicates(int[] nums) {
2 if (nums.length == 0) return 0;
3 int slow = 0;
4 for (int fast = 1; fast < nums.length; fast++) {
5 if (nums[fast] != nums[slow]) {
6 slow++;
7 nums[slow] = nums[fast];
8 }
9 }
10 return slow + 1;
11}Recognition signal: the problem involves a sorted array, a linked list, or asks for pairs/triplets that satisfy a condition. If brute force is O(n²), two pointers often gives O(n).
3Sum is a classic extension. Sort the array first, fix one element with a loop, then use two pointers for the remaining pair. Sorting costs O(n log n) but removes the need for a hash set and handles duplicates cleanly.
1public List<List<Integer>> threeSum(int[] nums) {
2 Arrays.sort(nums);
3 List<List<Integer>> result = new ArrayList<>();
4
5 for (int i = 0; i < nums.length - 2; i++) {
6 if (i > 0 && nums[i] == nums[i - 1]) continue; // skip duplicates
7
8 int left = i + 1, right = nums.length - 1;
9 while (left < right) {
10 int sum = nums[i] + nums[left] + nums[right];
11 if (sum == 0) {
12 result.add(Arrays.asList(nums[i], nums[left], nums[right]));
13 while (left < right && nums[left] == nums[left + 1]) left++;
14 while (left < right && nums[right] == nums[right - 1]) right--;
15 left++; right--;
16 } else if (sum < 0) left++;
17 else right--;
18 }
19 }
20 return result;
21}One of the hardest two-pointer problems. Water at position i equals min(maxLeft[i], maxRight[i]) - height[i]. The two-pointer approach avoids precomputing these arrays by tracking running maximums.
1public int trap(int[] height) {
2 int left = 0, right = height.length - 1;
3 int maxLeft = 0, maxRight = 0, water = 0;
4
5 while (left < right) {
6 if (height[left] < height[right]) {
7 if (height[left] >= maxLeft) maxLeft = height[left];
8 else water += maxLeft - height[left];
9 left++;
10 } else {
11 if (height[right] >= maxRight) maxRight = height[right];
12 else water += maxRight - height[right];
13 right--;
14 }
15 }
16 return water;
17}More in DSA