left, right = 0, len(arr)-1; while left < right: move based on condition| Pattern | Pointer Init | Movement Rule | Time | Space | Classic Problems |
|---|---|---|---|---|---|
| Opposite ends (converging) | left=0, right=n-1 | Move the pointer that improves the objective | O(n) | O(1) | Two Sum II, Container With Most Water, Trapping Rain Water, Valid Palindrome |
| Same direction (read/write) | slow=0, fast=0 | Fast scans all elements; slow marks write position | O(n) | O(1) | Remove Duplicates from Sorted Array, Remove Element, Move Zeroes |
| Fast/slow (cycle detection) | slow=head, fast=head | Slow moves 1 step, fast moves 2 steps | O(n) | O(1) | Linked List Cycle, Find the Duplicate Number, Happy Number |
| Fast/slow (midpoint) | slow=head, fast=head | Slow moves 1, fast moves 2; stop when fast reaches end | O(n) | O(1) | Middle of the Linked List, Palindrome Linked List |
| Partitioning | lo=0, hi=n-1 | Swap elements that belong on the other side | O(n) | O(1) | Sort Colors (Dutch National Flag), Partition Array |
| Merge (two arrays) | i=0, j=0 | Advance the pointer with the smaller element | O(n+m) | O(n+m) | Merge Sorted Array, Intersection of Two Arrays |
| Expand from center | left=center, right=center | Expand outward while condition holds | O(n²) total | O(1) | Longest Palindromic Substring |
| Three pointers | lo=0, mid=0, hi=n-1 | Partition into three regions | O(n) | O(1) | Sort Colors, Dutch National Flag |
START
|-- Is the input a linked list?
| |-- YES: Need cycle detection?
| | |-- YES --> Fast/slow pointers (Floyd's algorithm)
| | |-- NO: Need the middle node?
| | |-- YES --> Fast/slow (stop when fast reaches end)
| | |-- NO --> Same-direction pointers
| |-- NO (array or string) |
|-- Is the input sorted (or can you sort it)?
| |-- YES: Looking for a pair that satisfies a condition?
| | |-- YES --> Opposite-ends (converging) pointers
| | |-- NO: Need to remove/deduplicate in-place?
| | |-- YES --> Same-direction read/write pointers
| | |-- NO --> Consider binary search instead
| |-- NO (unsorted):
| |-- Can you afford O(n log n) sorting first?
| | |-- YES --> Sort, then use converging pointers
| | |-- NO --> Use a hash map/set instead (O(n) time, O(n) space)
|-- Need to partition into regions (e.g., <pivot, ==pivot, >pivot)?
| |-- YES --> Three-pointer / Dutch National Flag
|-- Need longest/shortest substring with a constraint?
| |-- YES --> Sliding window (see related unit)
|-- DEFAULT --> Brute force or hash map
Determine which variant fits your problem. If you need to find a pair in a sorted array, use converging pointers. If you need to detect a cycle, use fast/slow. If you need to remove elements in-place, use read/write pointers. [src1]
Verify: Can you express the loop invariant? e.g., "Everything to the left of slow is processed and valid."
For converging: left = 0, right = len(arr) - 1. For same-direction: slow = 0, fast = 0 (or fast = 1). For linked lists: slow = head, fast = head. [src2]
Verify: Both pointers point to valid positions in the data structure before the loop starts.
Converging: while left < right. Same-direction: while fast < len(arr). Linked list: while fast is not None and fast.next is not None. [src5]
Verify: The loop terminates in all cases. Converging pointers always reduce the gap by at least 1 per iteration.
Inside the loop, decide which pointer to advance based on the problem condition. For converging pointers on two-sum: if arr[left] + arr[right] < target, increment left; if greater, decrement right; if equal, return the pair. [src1]
Verify: Each iteration advances at least one pointer, guaranteeing O(n) termination.
Empty input, single element, all duplicates, already-at-target. For linked lists: null head, single node, cycle that includes the head. [src3]
Verify: Test with arrays of length 0, 1, 2 and linked lists with 0, 1, 2 nodes.
# Input: sorted array of integers, integer target
# Output: tuple of 1-indexed positions (i, j) where arr[i]+arr[j]==target
def two_sum_sorted(numbers: list[int], target: int) -> tuple[int, int]:
left, right = 0, len(numbers) - 1
while left < right:
current = numbers[left] + numbers[right]
if current == target:
return (left + 1, right + 1) # 1-indexed
elif current < target:
left += 1 # need a larger sum
else:
right -= 1 # need a smaller sum
raise ValueError("No solution found")
# Input: list of non-negative integers representing heights
# Output: maximum water area between any two lines
def max_area(height: list[int]) -> int:
left, right = 0, len(height) - 1
best = 0
while left < right:
width = right - left
h = min(height[left], height[right])
best = max(best, width * h)
if height[left] < height[right]:
left += 1 # move the shorter line inward
else:
right -= 1
return best
// Input: sorted array of integers (nums)
// Output: length of array with duplicates removed; nums mutated in-place
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let slow = 0; // write pointer
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) { // found a new unique element
slow++;
nums[slow] = nums[fast]; // write it at the next position
}
}
return slow + 1; // length of unique portion
}
# Input: head of a singly linked list (may contain a cycle)
# Output: True if cycle exists, False otherwise
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle(head: ListNode | None) -> bool:
slow = fast = head
while fast is not None and fast.next is not None:
slow = slow.next # 1 step
fast = fast.next.next # 2 steps
if slow is fast: # pointers met inside the cycle
return True
return False # fast reached end; no cycle
// Input: string s with mixed characters
// Output: true if s is a palindrome considering only alphanumeric chars
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left)))
left++; // skip non-alphanumeric
while (left < right && !Character.isLetterOrDigit(s.charAt(right)))
right--; // skip non-alphanumeric
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right)))
return false;
left++;
right--;
}
return true;
}
# BAD -- two-pointer converging requires sorted input
def two_sum_wrong(nums, target):
left, right = 0, len(nums) - 1
while left < right:
s = nums[left] + nums[right]
if s == target:
return [left, right]
elif s < target:
left += 1
else:
right -= 1
return [] # may miss valid pairs because array is unsorted
# GOOD -- sort first, then use converging pointers
def two_sum_correct(nums, target):
indexed = sorted(enumerate(nums), key=lambda x: x[1])
left, right = 0, len(indexed) - 1
while left < right:
s = indexed[left][1] + indexed[right][1]
if s == target:
return [indexed[left][0], indexed[right][0]]
elif s < target:
left += 1
else:
right -= 1
return []
# OR: use a hash map for O(n) without sorting
def two_sum_hashmap(nums, target):
seen = {}
for i, n in enumerate(nums):
complement = target - n
if complement in seen:
return [seen[complement], i]
seen[n] = i
return []
# BAD -- produces duplicate triplets
def three_sum_wrong(nums, target=0):
nums.sort()
result = []
for i in range(len(nums) - 2):
left, right = i + 1, len(nums) - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result # contains duplicate triplets
# GOOD -- skip duplicates at all three positions
def three_sum_correct(nums, target=0):
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]: # skip duplicate i
continue
left, right = i + 1, len(nums) - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1 # skip duplicate left
while left < right and nums[right] == nums[right - 1]:
right -= 1 # skip duplicate right
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result
# BAD -- returns the meeting point, NOT the cycle start
def find_cycle_start_wrong(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return slow # this is the meeting point, NOT the cycle start
return None
# GOOD -- Floyd's algorithm phase 2: find the cycle entry
def find_cycle_start_correct(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
# Phase 2: move one pointer to head, advance both by 1
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow # this is the actual cycle entry node
return None
left <= right instead of left < right for pair-finding causes accessing the same element twice. Fix: use left < right for distinct pairs. [src1]sort() first or verify the input is pre-sorted. [src3]arr[fast] while also reading from arr[slow] when slow == fast causes data corruption. Fix: use distinct read and write pointers that never overlap incorrectly. [src4]| Use When | Don't Use When | Use Instead |
|---|---|---|
| Finding a pair in a sorted array with a target sum | Array is unsorted and you cannot afford O(n log n) sort | Hash map (O(n) time, O(n) space) |
| Removing duplicates or elements in-place from a sorted array | You need to preserve original indices or order of unsorted elements | Filter into a new array |
| Detecting cycles in a singly linked list | Working with a doubly linked list or graph | Hash set / DFS with visited set |
| Checking if a string is a palindrome | Need to find all palindromic substrings | Expand-from-center or Manacher's algorithm |
| Merging two sorted arrays | Arrays are unsorted | Sort first, or use a heap for k-way merge |
| Partitioning an array (Dutch National Flag) | Need stable partitioning (preserve relative order) | Stable partition with O(n) extra space |
| Container with most water / trapping rain water | Problem has non-monotonic objective with no greedy elimination | Dynamic programming or monotonic stack |