2.1 Two Pointer
The Two Pointer technique uses two indices (pointers) to traverse a data structure, often in a single pass, to solve problems efficiently. This technique is particularly powerful for array and linked list problems, reducing time complexity from O(n^2) to O(n) by avoiding nested loops.
2.1.1 Core Concept
Instead of using nested loops to compare elements, two pointers work together to:
- Converge from opposite ends (opposite direction)
- Move at different speeds (fast-slow)
- Define a window (sliding window)
- Track positions in different structures (parallel traversal)
2.1.2 Common Patterns
2.1.2.1 Pattern 1: Opposite Direction (Converging Pointers)
When to use: Sorted arrays, finding pairs/triplets, container problems
How it works: Start pointers at opposite ends and move them toward each other based on a condition.
Template:
int left = 0, right = array.length - 1;
while (left < right) {
// Evaluate current state
int result = computeResult(array[left], array[right]);
// Move pointers based on condition
if (condition) {
left++; // Move left pointer right
} else {
right--; // Move right pointer left
}
}Key insight: In sorted arrays, pointer movement is monotonic - moving the left pointer increases values, moving the right pointer decreases values.
Examples:
- Two Sum II - Sorted Array: Find pair summing to target
- If
array[left] + array[right] < target: need larger sum, moveleft++ - If
array[left] + array[right] > target: need smaller sum, moveright-- - Time: O(n), Space: O(1)
- If
- Container With Most Water: Maximize area between two lines
- Area =
(right - left) × min(height[left], height[right]) - Always move the pointer with smaller height (greedy optimization)
- Time: O(n), Space: O(1)
- Area =
- 3Sum: Find all unique triplets summing to zero
- Fix one element, use two pointers on remaining sorted subarray
- Skip duplicates to ensure unique triplets
- Time: O(n^2), Space: O(1)
- Sort Array by Parity: Move even numbers to front
- Left pointer finds odd numbers, right pointer finds even numbers
- Swap when both found, continue until pointers cross
- Time: O(n), Space: O(1)
Complexity Analysis:
- Time: O(n) for single pass, O(n^2) when combined with outer loop (3Sum, 4Sum)
- Space: O(1) - only pointer variables
2.1.2.2 Pattern 2: Same Direction (Fast-Slow Pointers)
When to use: In-place array modification, linked list cycle detection, finding middle element
How it works: Both pointers start at the same position but move at different speeds or under different conditions.
Template:
int slow = 0, fast = 0;
while (fast < array.length) {
// Process element at fast pointer
if (shouldKeep(array[fast])) {
array[slow] = array[fast];
slow++;
}
fast++;
}
// Result is in array[0...slow-1]Key insight: The slow pointer tracks the “write position” for valid elements, while the fast pointer scans ahead.
Examples:
- Remove Duplicates from Sorted Array: Remove duplicates in-place
- Slow pointer tracks position for next unique element
- Fast pointer scans for new unique values
- Time: O(n), Space: O(1)
- Linked List Cycle (Floyd’s Cycle Detection): Detect cycle in linked list
- Fast pointer moves 2 steps, slow pointer moves 1 step
- If cycle exists, pointers will meet
- Time: O(n), Space: O(1)
- Palindrome Linked List: Check if linked list is palindrome
- Use fast-slow to find middle
- Reverse second half, compare with first half
- Time: O(n), Space: O(1)
Complexity Analysis:
- Time: O(n) - single pass with both pointers
- Space: O(1) - only pointer variables
2.1.2.3 Pattern 3: Sliding Window (Dynamic Two Pointers)
When to use: Subarray/substring problems with contiguous elements, optimization problems
How it works: Two pointers define a window that expands (move right) or contracts (move left) based on conditions.
Template:
int left = 0, result = 0;
for (int right = 0; right < array.length; right++) {
// Expand window by including array[right]
addToWindow(array[right]);
// Contract window while invalid
while (windowInvalid()) {
removeFromWindow(array[left]);
left++;
}
// Update result with current valid window
result = updateResult(result, right - left + 1);
}Key insight: The window expands to explore possibilities and contracts to maintain validity, ensuring each element is visited at most twice (once by right, once by left).
Examples:
- Minimum Size Subarray Sum: Find minimum subarray length with sum \(\ge\) target
- Expand: add elements until sum \(\ge\) target
- Contract: remove from left while maintaining sum \(\ge\) target
- Time: O(n), Space: O(1)
- Longest Substring Without Repeating: Find longest substring with unique characters
- Expand: add characters to window
- Contract: shrink from left when duplicate found
- Use hash set to track characters in current window
- Time: O(n), Space: O(min(n, charset size))
- Number of Substrings All Three Chars: Count substrings containing ‘a’, ‘b’, ‘c’
- When window
[left, right]is valid (contains all three), all substrings extending to end are valid - Add
n - rightto result (not just 1) - Time: O(n), Space: O(1)
- When window
Complexity Analysis:
- Time: O(n) - each element added once (right) and removed once (left), total 2n operations
- Space: O(k) where k is auxiliary data structure size (hash map, frequency array)
2.1.2.4 Pattern 4: Parallel Traversal (Two Lists)
When to use: Merging sorted lists, comparing sequences, simultaneous traversal
How it works: Each pointer tracks position in a different data structure, advancing independently.
Template:
int i = 0, j = 0;
while (i < list1.length && j < list2.length) {
if (compareCondition(list1[i], list2[j])) {
processAndAdvance(list1[i]);
i++;
} else {
processAndAdvance(list2[j]);
j++;
}
}
// Handle remaining elements in either listExamples:
- Merge Two Sorted Lists: Merge two sorted linked lists
- Compare current nodes from both lists
- Append smaller node to result, advance that pointer
- Time: O(m + n), Space: O(1)
- Interval List Intersections: Find interval intersections
- Compare intervals from both lists
- Advance pointer of interval that ends first
- Time: O(m + n), Space: O(1)
Complexity Analysis:
- Time: O(m + n) where m, n are sizes of the two structures
- Space: O(1) - only pointer variables (excluding output)
2.1.3 Two Pointer vs Other Techniques
| Aspect | Two Pointer | Hash Table | Sorting |
|---|---|---|---|
| Time Complexity | O(n) or O(n^2) with outer loop | O(n) average | O(n log n) |
| Space Complexity | O(1) | O(n) | O(1) or O(n) |
| When to Use | Sorted data, in-place modification, pairs/triplets | Fast lookups, unsorted data | Need ordering, multiple searches |
| Limitation | Requires sorted data or special structure | Extra space, no ordering | Destroys original order |
Decision tree:
- Need to preserve original indices? → Use Hash Table (e.g., Two Sum I)
- Array already sorted or can be sorted? → Use Two Pointer (e.g., Two Sum II, 3Sum)
- Need all pairs/triplets from sorted data? → Use Two Pointer with duplicate skipping (e.g., 3Sum)
- Subarray/substring problem with contiguous elements? → Use Sliding Window (e.g., Minimum Size Subarray Sum)
- In-place modification required? → Use Same-Direction Two Pointer (e.g., Remove Duplicates)
2.1.4 Common Pitfalls
Forgetting to skip duplicates: In problems like 3Sum, must skip duplicates at all pointer levels
Incorrect window validity: In sliding window, ensure condition is checked before and after window modification
Off-by-one errors: Carefully handle boundary conditions
Not considering all valid substrings: In problems like Number of Substrings All Three Chars, when window becomes valid, all extensions are also valid
2.1.5 Summary
The Two Pointer technique is a fundamental optimization that:
- Reduces time complexity from O(n^2) to O(n) by eliminating nested loops
- Minimizes space usage to O(1) by avoiding auxiliary data structures
- Works best with sorted data or problems allowing in-place modification
- Has four main patterns: opposite direction, same direction, sliding window, parallel traversal
Master these patterns and you’ll recognize two-pointer opportunities in most array, string, and linked list problems.