← Назад к вопросам

Проверка степени двойки

1.0 Junior🔥 201 комментариев
#Коллекции и структуры данных#Язык Swift

Условие

Напишите функцию на Swift, которая определяет, является ли целое число степенью двойки.

Примеры:

  • Вход: 1 → Выход: true (2^0 = 1)
  • Вход: 16 → Выход: true (2^4 = 16)
  • Вход: 3 → Выход: false
  • Вход: 0 → Выход: false
  • Вход: -16 → Выход: false

Требования:

  • Решение должно работать за O(1) по времени
  • Не использовать циклы или рекурсию

Подсказка: Используйте побитовые операции. Подумайте, как выглядят степени двойки в двоичном представлении.

Бонус: Объясните, почему работает выражение n & (n - 1).

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Подход

Это классическая задача на побитовые операции. Ключевая идея основана на том, что степени двойки в двоичном представлении всегда имеют ровно один бит, установленный в 1, а все остальные биты равны 0.

Примеры в двоичной системе:

  • 1 = 1
  • 2 = 10
  • 4 = 100
  • 8 = 1000
  • 16 = 10000

Оптимальное решение O(1)

func isPowerOfTwo(_ n: Int) -> Bool {
    return n > 0 && (n & (n - 1)) == 0
}

Как это работает:

Битовое выражение n & (n - 1) удаляет самый правый установленный бит из числа n.

Пример с 16 (binary: 10000):

n     = 16      →  10000 (binary)
n - 1 = 15      →  01111 (binary)
n & (n-1) = 0   →  00000 (binary)

Для степени двойки (один бит установлен):

  • n & (n - 1) всегда даёт 0

Для не-степени двойки (несколько битов установлены):

  • n & (n - 1) даёт ненулевое значение

Пример с 12 (binary: 1100):

n     = 12      →  1100 (binary)
n - 1 = 11      →  1011 (binary)
n & (n-1) = 8   →  1000 (binary) ≠ 0

Альтернативные решения

1. Через логарифм:

func isPowerOfTwo(_ n: Int) -> Bool {
    guard n > 0 else { return false }
    let log = log2(Double(n))
    return log == floor(log)
}

Этот подход работает, но имеет проблемы с точностью чисел с плавающей точкой.

2. Через битовый счёт:

func isPowerOfTwo(_ n: Int) -> Bool {
    guard n > 0 else { return false }
    // Считаем количество установленных битов
    return n.nonzeroBitCount == 1
}

Это читаемо и тоже O(1) на большинстве платформ.

3. Через цикл (O(log n)):

func isPowerOfTwo(_ n: Int) -> Bool {
    guard n > 0 else { return false }
    var num = n
    while num % 2 == 0 {
        num /= 2
    }
    return num == 1
}

Это работает, но медленнее (не O(1)).

Тестирование

// Степени двойки (должны быть true)
print(isPowerOfTwo(1))      // true  (2^0)
print(isPowerOfTwo(2))      // true  (2^1)
print(isPowerOfTwo(4))      // true  (2^2)
print(isPowerOfTwo(8))      // true  (2^3)
print(isPowerOfTwo(16))     // true  (2^4)
print(isPowerOfTwo(256))    // true  (2^8)
print(isPowerOfTwo(1024))   // true  (2^10)

// Не степени двойки (должны быть false)
print(isPowerOfTwo(0))      // false (не положительное)
print(isPowerOfTwo(3))      // false
print(isPowerOfTwo(5))      // false
print(isPowerOfTwo(12))     // false (1100 в двоичной)
print(isPowerOfTwo(15))     // false (1111 в двоичной)
print(isPowerOfTwo(-16))    // false (отрицательное)

// Граничные случаи
print(isPowerOfTwo(Int.max))     // false
print(isPowerOfTwo(Int.min))     // false
print(isPowerOfTwo(1 << 30))     // true (2^30)

Почему работает n & (n - 1) == 0?

Математическое объяснение:

Вычитание 1 из степени двойки в двоичной системе даёт очень специфичный результат:

  • Все нули справа от единицы становятся единицами
  • Сама единица становится нулём

Пример:

16 (10000) - 1 = 15 (01111)
32 (100000) - 1 = 31 (011111)
64 (1000000) - 1 = 63 (0111111)

После операции AND все биты становятся 0, потому что нет совпадающих единиц.

Для других чисел остаётся хотя бы один общий бит:

12 (1100) - 1 = 11 (1011)
12 & 11 = 1000 (8) ≠ 0

Производительность

// Сравнение скорости
import Foundation

func measureTime(name: String, _ closure: () -> Void) {
    let start = Date()
    closure()
    let elapsed = Date().timeIntervalSince(start)
    print("\(name): \(elapsed) seconds")
}

measureTime(name: "Bitwise") {
    for n in 1...1_000_000_000 {
        _ = n > 0 && (n & (n - 1)) == 0
    }
}

measureTime(name: "nonzeroBitCount") {
    for n in 1...1_000_000_000 {
        _ = n > 0 && n.nonzeroBitCount == 1
    }
}

Практическое применение

Это знание используется в:

  • Хэш-таблицах — выделение памяти степенями двойки
  • Graphics API — текстуры часто требуют степени двойки
  • Битовых операциях — оптимизация по памяти и производительности
  • Алгоритмах — поиск, сортировка, битовые маски

Рекомендация для интервью

Используйте первое решение:

func isPowerOfTwo(_ n: Int) -> Bool {
    return n > 0 && (n & (n - 1)) == 0
}

Это:

  • Демонстрирует знание побитовых операций
  • O(1) по времени и памяти
  • Очень кратко и элегантно
  • Показывает глубокое понимание двоичной системы

Если интервьюер спросит о альтернативах:

// Более явный вариант
func isPowerOfTwo(_ n: Int) -> Bool {
    guard n > 0 else { return false }
    return n.nonzeroBitCount == 1
}

Этот вариант более читаем для тех, кто не знаком с побитовыми трюками.

Объяснение на интервью

"Степени двойки в двоичной системе имеют ровно один установленный бит. Когда мы вычитаем 1 из степени двойки, все биты справа от этого единственного бита становятся 1, а сам бит становится 0. Операция AND между исходным числом и результатом вычитания даёт 0, только если исходное число было степенью двойки."