Pattern cheatsheets

Two Pointers

👉 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

Python template
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)

Python template
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)

Python template
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

Python template
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

OperationTimeSpace
Opposite-ends scanO(n)O(1)
Read/write filter in placeO(n)O(1)
Sort then two pointersO(n log n)O(1)–O(n) for the sort
3Sum (fix one + scan)O(n²)O(1) extra
k-SumO(n^(k-1))O(k) recursion
Fast & slow cycle checkO(n)O(1)
Brute force over all pairsO(n²)O(1)

Watch for

  • Loop condition: while lo < hi for 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).