Побитовые операции и трюки. XOR, AND, OR, сдвиги, битовые маски.
Побитовые операции: AND (&), OR (|), XOR (^), NOT (~), сдвиги (<<, >>). Эффективные решения через битовые трюки.
Bit Manipulation — это техника решения задач на битовом уровне. Побитовые операции позволяют выполнять вычисления быстрее и компактнее, чем арифметические операции.
| Операция | Обозначение | Пример | Результат |
|---|---|---|---|
| AND | & | 5 & 3 | 1 (101 & 011 = 001) |
| OR | | | 5 | 3 | 7 (101 | 011 = 111) |
| XOR | ^ | 5 ^ 3 | 6 (101 ^ 011 = 110) |
| NOT | ~ | ~5 | -6 (дополнение до 2) |
| Left Shift | << | 5 << 1 | 10 (101 << 1 = 1010) |
| Right Shift | >> | 5 >> 1 | 2 (101 >> 1 = 10) |
a ^ a = 0 # XOR с собой = 0
a ^ 0 = a # XOR с 0 = сам элемент
a ^ b = b ^ a # Коммутативность
(a ^ b) ^ c = a ^ (b ^ c) # Ассоциативность
# Проверка на степень двойки
n > 0 and (n & (n - 1)) == 0
# Убрать младший 1
n & (n - 1)
# Получить младший 1
n & -n
# Проверить чётность
n & 1 == 0
# Умножить на 2
n << 1
# Разделить на 2
n >> 1
# Проверить бит i
(n >> i) & 1
# Установить бит i
n | (1 << i)
# Сбросить бит i
n & ~(1 << i)
# Инвертировать бит i
n ^ (1 << i)Условие:
Дан непустой массив целых чисел nums, где каждый элемент встречается дважды, кроме одного. Найдите этот уникальный элемент.
Примеры:
Ввод: nums = [2,2,1]
Вывод: 1
Ввод: nums = [4,1,2,1,2]
Вывод: 4
Ввод: nums = [1]
Вывод: 1
Ограничения:
💡 Подсказка: Используйте свойство XOR: a ^ a = 0, a ^ 0 = a. XOR всех элементов сократит пары.
nums = [4, 1, 2, 1, 2]
XOR всех элементов:
4 ^ 1 ^ 2 ^ 1 ^ 2
Группируем:
4 ^ (1 ^ 1) ^ (2 ^ 2)
4 ^ 0 ^ 0
4
Пары сократились, остался уникальный элемент!
Решение:
def singleNumber(nums):
result = 0
for num in nums:
result ^= num
return resultОбъяснение:
Сложность:
Условие: Напишите функцию, которая принимает беззнаковое целое число и возвращает количество единичных битов в его двоичном представлении (вес Хэмминга).
Примеры:
Ввод: n = 00000000000000000000000000001011 (11)
Вывод: 3
Ввод: n = 00000000000000000000000010000000 (128)
Вывод: 1
Ввод: n = 11111111111111111111111111111101
Вывод: 31
Ограничения:
💡 Подсказка: Используйте трюк n &= n - 1, который убирает младший бит 1.
Решение:
def hammingWeight(n):
count = 0
while n:
n &= n - 1 # Убирает младший 1
count += 1
return countОбъяснение:
Сложность:
Условие:
Дано целое число n. Верните массив длины n + 1, где array[i] — количество единичных битов в числе i.
Примеры:
Ввод: n = 2
Вывод: [0,1,1]
Объяснение: 0→0, 1→1, 2→10
Ввод: n = 5
Вывод: [0,1,1,2,1,2]
Объяснение: 0→0, 1→1, 2→10, 3→11, 4→100, 5→101
Ограничения:
💡 Подсказка: dp[i] = dp[i >> 1] + (i & 1). Биты(i) = биты(i/2) + младший бит.
Решение:
def countBits(n):
dp = [0] * (n + 1)
for i in range(1, n + 1):
dp[i] = dp[i >> 1] + (i & 1)
return dpОбъяснение:
Сложность:
Условие: Разверните биты беззнакового 32-битного целого числа.
Примеры:
Ввод: n = 00000010100101000001111010011100
Вывод: 964176192 (00111001011110000010100101000000)
Ввод: n = 11111111111111111111111111111101
Вывод: 3221225471 (10111111111111111111111111111111)
Ограничения:
💡 Подсказка: Для каждого из 32 бит: сдвиньте результат влево, добавьте младший бит n, сдвиньте n вправо.
Решение:
def reverseBits(n):
result = 0
for _ in range(32):
result = (result << 1) | (n & 1)
n >>= 1
return resultОбъяснение:
Сложность:
Условие:
Дан массив nums из n различных чисел в диапазоне [0, n]. Найдите единственное пропущенное число.
Примеры:
Ввод: nums = [3,0,1]
Вывод: 2
Ввод: nums = [0,1]
Вывод: 2
Ввод: nums = [9,6,4,2,3,5,7,0,1]
Вывод: 8
Ограничения:
💡 Подсказка: XOR всех индексов и значений. Пары сократятся, останется пропущенное число.
Решение:
def missingNumber(nums):
n = len(nums)
result = n # Включаем n
for i in range(n):
result ^= i ^ nums[i]
return resultОбъяснение:
Сложность:
Условие:
Даны два целых числа a и b. Верните их сумму без использования операторов + и -.
Примеры:
Ввод: a = 1, b = 2
Вывод: 3
Ввод: a = 2, b = 3
Вывод: 5
Ограничения:
💡 Подсказка: a ^ b — сумма без переноса. (a & b) << 1 — перенос. Повторяйте, пока перенос не станет 0.
Решение:
def getSum(a, b):
# Для Python с отрицательными числами
MASK = 0xFFFFFFFF
MAX_INT = 0x7FFFFFFF
while b != 0:
carry = (a & b) << 1
a = (a ^ b) & MASK
b = carry & MASK
return a if a <= MAX_INT else ~(a ^ MASK)Объяснение:
Сложность:
Условие:
Даны два целых числа left и right. Верните bitwise AND всех чисел в диапазоне [left, right] включительно.
Примеры:
Ввод: left = 5, right = 7
Вывод: 4
Объяснение: 5 & 6 & 7 = 101 & 110 & 111 = 100 = 4
Ввод: left = 0, right = 0
Вывод: 0
Ввод: left = 1, right = 2147483647
Вывод: 0
Ограничения:
💡 Подсказка: AND всех чисел в диапазоне = общий префикс битов left и right. Сдвигайте, пока не совпадут.
Решение:
def rangeBitwiseAnd(left, right):
shift = 0
# Находим общий префикс
while left < right:
left >>= 1
right >>= 1
shift += 1
return left << shiftОбъяснение:
Сложность:
Условие:
Даны две строки s и t. Строка t получена перемешиванием s с добавлением одной буквы. Найдите добавленную букву.
Примеры:
Ввод: s = "abcd", t = "abcde"
Вывод: "e"
Ввод: s = "", t = "y"
Вывод: "y"
Ограничения:
💡 Подсказка: XOR всех символов s + t. Пары сократятся, останется добавленный символ.
Решение:
def findTheDifference(s, t):
result = 0
for c in s + t:
result ^= ord(c)
return chr(result)Объяснение:
Сложность:
Условие:
Дано целое число n. Верните true, если оно является степенью двойки.
Примеры:
Ввод: n = 1
Вывод: true (2⁰ = 1)
Ввод: n = 16
Вывод: true (2⁴ = 16)
Ввод: n = 3
Вывод: false
Ввод: n = 4
Вывод: true (2² = 4)
Ограничения:
💡 Подсказка: Степени двойки имеют ровно один бит 1. Проверьте: n > 0 and (n & (n - 1)) == 0.
n = 8 (1000 в двоичной)
n - 1 = 7 (0111 в двоичной)
n & (n-1):
1000 (8)
& 0111 (7)
------
0000 (0) ← степень двойки!
n = 6 (110 в двоичной)
n - 1 = 5 (101 в двоичной)
n & (n-1):
110 (6)
& 101 (5)
-----
100 (4) ← не 0, не степень двойки!
Решение:
def isPowerOfTwo(n):
return n > 0 and (n & (n - 1)) == 0Объяснение:
Сложность:
Условие:
Дан целочисленный массив nums. Верните максимальный результат nums[i] XOR nums[j], где 0 <= i <= j < n.
Примеры:
Ввод: nums = [3,10,5,25,2,8]
Вывод: 28
Объяснение: 5 XOR 25 = 28
Ввод: nums = [14,70,53,83,49,91,36,80,92,51,66,70]
Вывод: 127
Ограничения:
💡 Подсказка: Для каждого бита от старшего к младшему: пытаемся получить 1 в результате. Проверяем, есть ли пара с нужным префиксом.
Решение:
def findMaximumXOR(nums):
result = 0
for bit in range(31, -1, -1):
result <<= 1
# Префиксы чисел с текущим битом
prefixes = {num >> bit for num in nums}
# Пытаемся получить 1 в текущем бите
result += any(result ^ 1 ^ p in prefixes for p in prefixes)
return resultОбъяснение:
Сложность:
Условие: Бинарные часы имеют 4 светодиода для часов (0-11) и 6 для минут (0-59).
Дано число turnedOn — количество горящих светодиодов. Верните все возможные времена, которые могут быть показаны.
Примеры:
Ввод: turnedOn = 1
Вывод: ["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
Ввод: turnedOn = 9
Вывод: []
Ограничения:
💡 Подсказка: Переберите все часы (0-11) и минуты (0-59). Проверьте, что количество бит 1 равно turnedOn.
Решение:
def readBinaryWatch(turnedOn):
result = []
for h in range(12):
for m in range(60):
if bin(h).count('1') + bin(m).count('1') == turnedOn:
result.append(f"{h}:{m:02d}")
return resultОбъяснение:
Сложность:
Условие:
Дан массив arr и массив запросов queries, где queries[i] = [left, right]. Для каждого запроса вычислите XOR элементов от left до right.
Примеры:
Ввод: arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
Вывод: [2,7,14,8]
Объяснение:
[0,1]: 1 ^ 3 = 2
[1,2]: 3 ^ 4 = 7
[0,3]: 1 ^ 3 ^ 4 ^ 8 = 14
[3,3]: 8 = 8
Ограничения:
💡 Подсказка: Используйте префиксный XOR. XOR[left, right] = prefix[right+1] ^ prefix[left].
Решение:
def xorQueries(arr, queries):
# Префиксный XOR
prefix = [0]
for num in arr:
prefix.append(prefix[-1] ^ num)
result = []
for left, right in queries:
# XOR диапазона = prefix[right+1] ^ prefix[left]
result.append(prefix[right + 1] ^ prefix[left])
return resultОбъяснение:
Сложность:
# Проверка на степень двойки
n > 0 and (n & (n - 1)) == 0
# Убрать младший 1
n &= n - 1
# Получить младший 1
n & -n
# Проверить чётность
n & 1 == 0
# Умножить на 2
n << 1
# Разделить на 2
n >> 1
# Проверить бит i
(n >> i) & 1
# Установить бит i
n | (1 << i)
# Сбросить бит i
n & ~(1 << i)
# Инвертировать бит i
n ^ (1 << i)| Задача | Трюк |
|---|---|
| Single Number | XOR всех элементов |
| Number of 1 Bits | n &= n-1 |
| Counting Bits | dp[i] = dp[i>>1] + (i&1) |
| Reverse Bits | Сдвиги 32 раза |
| Missing Number | XOR индексов и значений |
| Sum of Two Integers | XOR + AND << 1 |
| Range Bitwise AND | Общий префикс |
| Find Difference | XOR всех символов |
| Power of Two | n & (n-1) == 0 |
| Maximum XOR | Жадный по битам |
| Binary Watch | Перебор + count('1') |
| XOR Queries | Префиксный XOR |
Проблема: Не знают свойства XOR.
# ❌ Неправильно — используют hash set
def singleNumber(nums):
seen = set()
for num in nums:
if num in seen:
seen.remove(num)
else:
seen.add(num)
return seen.pop() # O(n) память
# ✅ Правильно — XOR
def singleNumber(nums):
result = 0
for num in nums:
result ^= num
return result # O(1) память!Почему это неправильно: XOR использует O(1) памяти.
Проблема: 0 & (0-1) = 0, но 0 не степень двойки.
# ❌ Неправильно
def isPowerOfTwo(n):
return (n & (n - 1)) == 0 # 0 вернёт True!
# ✅ Правильно
def isPowerOfTwo(n):
return n > 0 and (n & (n - 1)) == 0Почему это неправильно: 0 не является степенью двойки.
Проблема: >> сохраняет знак.
# ❌ Неправильно
n = -8
n >> 1 # -4 (знак сохраняется)
# ✅ Правильно — используйте >>> для логического сдвига
# В Python нет >>>, но можно использовать маску
n & 0xFFFFFFFF # Беззнаковое представлениеПочему это неправильно: Арифметический сдвиг сохраняет знак.
Проблема: Не проверяют n <= 0.
# ❌ Неправильно
def hammingWeight(n):
count = 0
while n:
n &= n - 1
count += 1
return count
# Для отрицательных n бесконечный цикл!
# ✅ Правильно
def hammingWeight(n):
count = 0
n = n & 0xFFFFFFFF # Беззнаковое
while n:
n &= n - 1
count += 1
return countПочему это неправильно: Отрицательные числа в дополнении до 2.
Проблема: & имеет меньший приоритет, чем ==.
# ❌ Неправильно
if n & 1 == 0: # n & (1 == 0) = n & 0 = 0!
# ✅ Правильно
if (n & 1) == 0: # Сначала &, потом ==Почему это неправильно: Приоритет операторов.
Условие: Дан целый массив, где каждый элемент встречается дважды, кроме одного. Найдите уникальный элемент.
Пример:
Ввод: nums = [4,1,2,1,2]
Вывод: 4
def singleNumber(nums):
result = 0
for num in nums:
result ^= num
return result
# Пример:
# [4,1,2,1,2]
# 0 ^ 4 = 4
# 4 ^ 1 = 5
# 5 ^ 2 = 7
# 7 ^ 1 = 6 (1 сократился)
# 6 ^ 2 = 4 (2 сократился)
# return 4Объяснение:
Сложность:
Bit Manipulation даёт O(1) или O(log n) решения.
Ключевые свойства:
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.