👉 Two Pointers
Two indices that only ever move one way turn an O(n²) pair search into a single O(n) pass.
Recognize the pattern
Sorted array + find a pair with a target sum → pointers from both ends
Palindrome / compare mirrored positions → both ends, walk inward
Remove, dedupe or partition in place with O(1) space → read/write pointers
All unique triplets (quadruplets) summing to a target → sort, fix one, two pointers on the rest
Best pair of positions (width × height, area) → shrink from both ends, move the limiting side
Sorted input with negatives, output sorted by absolute value → fill the result from the back
Cycle in a next-value sequence → fast & slow pointers
Opposite ends
def pair_sum(nums: list[int], target: int) -> list[int]:
lo, hi = 0, len(nums) - 1
while lo < hi:
s = nums[lo] + nums[hi]
if s == target:
return [lo, hi]
if s < target:
lo += 1 # nums[lo] too small for anyone
else:
hi -= 1 # nums[hi] too big for anyone
return []
Why moving a pointer is safe
Each move must discard a candidate that can't be in
any answer. Sum too small → nums[lo] fails even with
the biggest partner left. Container: the shorter wall
limits the height, and any other partner is narrower, so
the shorter wall is done. If you can't justify a move
this way, two pointers probably isn't the right tool.
Read / write (in place)
def keep_if(nums: list[int], val: int) -> int:
write = 0
for read in range(len(nums)):
if nums[read] != val: # keep test
nums[write] = nums[read]
write += 1
return write # nums[:write] is the answer
# dedupe sorted: keep if write == 0 or
# nums[read] != nums[write - 1]
# keep the rest (Move Zeroes): swap instead of copy
Sort + fix one + two pointers (3Sum)
def three_sum(nums: list[int]) -> list[list[int]]:
nums.sort()
res = []
for i in range(len(nums) - 2):
if i and nums[i] == nums[i - 1]:
continue # skip dup first
lo, hi = i + 1, len(nums) - 1
while lo < hi:
s = nums[i] + nums[lo] + nums[hi]
if s < 0:
lo += 1
elif s > 0:
hi -= 1
else:
res.append([nums[i], nums[lo], nums[hi]])
lo, hi = lo + 1, hi - 1
while lo < hi and nums[lo] == nums[lo - 1]:
lo += 1 # skip dup middle
return res
Fast & slow
def has_cycle(start, step) -> bool:
slow, fast = start, step(start)
while fast is not None and slow != fast:
slow = step(slow)
nxt = step(fast)
fast = step(nxt) if nxt is not None else None
return fast is not None
Operation costs
| Operation | Time | Space |
|---|---|---|
| Opposite-ends scan | O(n) | O(1) |
| Read/write filter in place | O(n) | O(1) |
| Sort then two pointers | O(n log n) | O(1)–O(n) for the sort |
| 3Sum (fix one + scan) | O(n²) | O(1) extra |
| k-Sum | O(n^(k-1)) | O(k) recursion |
| Fast & slow cycle check | O(n) | O(1) |
| Brute force over all pairs | O(n²) | O(1) |
Watch for
Loop condition:
while lo < hifor pairs of distinct positions;<=makes an element pair with itself.Every branch must move a pointer, or the loop never ends.
Skip duplicates against the previous value (
nums[i] == nums[i - 1]), not the next one, or you'll skip valid first uses.Sorting destroys the original indices; don't sort if the answer needs them.
Never
remove()from a list while looping over it; use read/write pointers.Check the index base of the answer (Two Sum II wants 1-indexed positions).