Признак в условии
- «за O(1) памяти», хотя напрашивается множество
- числа встречаются парами, кроме одного
- степень двойки, счёт единичных битов, флаги и маски
- нужно закодировать набор булевых признаков компактно
Приёмы, которые надо знать наизусть
| выражение | что делает |
|---|---|
a ^ a === 0, a ^ 0 === a | XOR уничтожает пары |
n & (n - 1) | снимает младший установленный бит |
n & -n | оставляет только младший установленный бит |
n >> 1 | делит на два, убирая младший бит |
n & 1 | младший бит: чётность |
mask & flag | проверка флага в маске |
mask | flag | установка флага |
mask & ~flag | снятие флага |
Шаблон
// единственное непарное число
return nums.reduce((acc, n) => acc ^ n, 0);
// степень двойки
return n > 0 && (n & (n - 1)) === 0;
// число единичных битов для всех i до n: динамика
out[i] = out[i >> 1] + (i & 1);
Сложность
O(1) на операцию, O(n) на проход. Память O(1) - в этом весь смысл.
Осторожно в JS
Побитовые операторы приводят число к 32-битному знаковому целому.
Для значений больше 2³¹−1 результат будет неожиданным; сдвиг >>> даёт беззнаковый.
Для больших чисел - BigInt, но битовые трюки с ним медленнее.
Частые ошибки
- забыта проверка на положительность: для нуля
n & (n - 1)тоже даёт ноль - путают
>>и>>>на отрицательных числах - сравнивают
mask & flagс единицей вместо самого flag - ломается на многобитных флагах
Мнемоника
«XOR съедает пары, минус один снимает младший бит»
Не путать с
- Хеш-таблица - битовая маска заменяет множество, когда элементов до 32–64 и они плотные
- Динамическое программирование - динамика по подмножествам - это динамика, у которой состояние хранится маской
Задачи на этот паттерн
- единственное непарное число
- число единичных битов
- степень двойки