Skip to the content.

Два указателя - это идея, в некоторых случаях позволяющая улучшить сложность решения с $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$ - максимальное значение каждого из указателей