Приём двух указателей двигает два индекса по массиву (или строке) согласованно и заменяет вложенные циклы (O(n^2)) одним проходом (O(n)). Две основные формы: с концов навстречу и быстрый/медленный.

![Движение указателей в [1, 3, 4, 6, 8, 11] при цели 10](img/lessons/algorithms/two-pointers-pair-sum-trace.svg)

С концов навстречу: пара с суммой

В отсортированном массиве начинаем с i = 0 и j = n - 1. Если сумма a[i] + a[j] меньше цели, то a[i] не образует пару ни с одним a[k] (k <= j): все они не больше a[j]; значит i можно отбросить. Если больше, симметрично отбрасываем j. Этот инвариант гарантирует корректность: члены искомой пары всегда в [i, j].

python
def pair_sum(a, target):
    i, j = 0, len(a) - 1
    while i < j:
        s = a[i] + a[j]
        if s == target:
            return (i, j)
        if s < target:
            i += 1              # need a bigger sum
        else:
            j -= 1              # need a smaller sum
    return None

data = [1, 3, 4, 6, 8, 11]
print(pair_sum(data, 10))
print(pair_sum(data, 20))
text
(2, 3)
None