Два указателя - это идея, в некоторых случаях позволяющая улучшить сложность решения с $O(n^2)$ до линейного алгоритма.
Опишем идею на примере.
Дан массив длины $n$ из целых положительных чисел и число $k$.
Нужно найти такие $i$, $j$ ($i <= j$), что $a_i + a_{i + 1} + \dots + a_j >= k$, a $j - i + 1$ минимально.
Первое, что приходит в голову, написать квадратичный алгоритм.
ans = (-float('inf'), float('inf'))
for i in range(n):
s = 0
for j in range(i, n):
s += a[j]
if s > k:
if j - i + 1 < ans[1] - ans[0] + 1:
ans = (i, j)
break
print(*ans)
Заметим, что мы фиксируем каждый раз $i$ и находим для него самый оптимальный $j$. Давайте введем функцию $j(i)$ равную этому самому оптимальному $j$.
Тогда ключевым для нас наблюдением будет следующий факт: $j(i) <= j(i + 1)$. Покажем это.
Для $j(i)$ верно
\[a_i + a_{i + 1} + \dots + a_{j(i)} >= k\]Заметим, что при всех $l = i + 1, \dots, j(i) - 1$
\[a_{i + 1} + \dots + a_l <= k\]А значит, $j(i) <= j(i + 1)$
Получается, что на самом деле мы можем “двигать” $j$ только вперед (не начиная каждый раз проверку сначала).
ans = (-float('inf'), float('inf'))
j = 0
s = 0
for i in range(n):
while j < n and s < k:
s += a[j]
j += 1
if s < k:
break
if j - i + 1 < ans[1] - ans[0] + 1:
ans = (i, j)
s -= a[i]
print(*ans)
Оценим сложность.
Каждый раз мы гарантированно увеличиваем или $i$, или $j$, при этом $i, j < n$.
Значит, выполняем не больше $O(n + n) = O(n)$ действий. Получили линейный алгоритм.
Указателями обычно называют эти самые $i$ и $j$, которые как бы указывают на инвариант и не уменьшаются, тем самым дают возможность решить задачу за линию.
Много указателей
Бывают задачи, где нужны сразу $k$ указателей. Тогда сложность полученного алгоритма будет $O(nk)$, где $n$ - максимальное значение каждого из указателей