Приём двух указателей двигает два индекса по массиву (или строке) согласованно и заменяет вложенные циклы (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].
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))(2, 3)
None