← Все вопросы

lower_bound и upper_bound в C++: чем отличаются и какие частые грабли?

Задан 16 месяцев назад1.1к просмотров2 ответа
12

Постоянно путаю lower_bound и upper_bound. Как точно запомнить, что каждый возвращает? И почему lower_bound на std::set иногда работает за O(n), хотя я думал, что бинпоиск всегда O(log n)?

2 ответа

18
✓ Принятый ответ — помог автору

Для отсортированного диапазона:

  • lower_bound(first, last, x) → итератор на первый элемент >= x (первая позиция, куда можно вставить x, не нарушив порядок, левее равных).
  • upper_bound(first, last, x) → итератор на первый элемент > x (вставка правее равных).

Отсюда полезные формулы:

  • Число элементов, равных x: upper_bound(...) - lower_bound(...).
  • Число элементов < x: lower_bound(...) - begin.
  • Число <= x: upper_bound(...) - begin.
vector<int> a = {1, 2, 2, 2, 5};
int lt2  = lower_bound(a.begin(), a.end(), 2) - a.begin(); // 1 (индекс первого 2)
int gt2  = upper_bound(a.begin(), a.end(), 2) - a.begin(); // 4
int cnt2 = gt2 - lt2; // 3 — сколько двоек

Сложность O(log n) для random-access итераторов (vector, array).

Грабля с set. Свободные функции std::lower_bound(s.begin(), s.end(), x) на set дают O(n)! Итераторы set — bidirectional, а не random-access, поэтому std::lower_bound делает O(log n) сравнений, но O(n) шагов итератора. Используй методы-члены: s.lower_bound(x) и s.upper_bound(x) — они O(log n) по дереву. То же для map. Это классический источник скрытого TLE.

7

Ещё две грабли:

  1. Перед lower_bound/upper_bound массив обязан быть отсортирован по тому же компаратору. Если сортировал по убыванию, передавай тот же компаратор третьим/четвёртым аргументом (greater<int>()), иначе UB и мусор.
  2. Проверяй, что итератор не равен end(), прежде чем разыменовывать: auto it = lower_bound(...); if (it != a.end() && *it == x) .... Очень частый RE/WA — обращение к *it, когда x больше всех элементов и it == end().

Ваш ответ

Войдите, чтобы ответить на вопрос.