Ответ 1
Вот небольшое замечание, которое вы можете использовать для этого во времени O (n). Представьте, что вы хотите знать, сколько 1 бита установлено в числе k, и что вы уже знаете, сколько 1 бита установлено в числах 0, 1, 2,..., k - 1. Если вы можете найти способ чтобы очистить любой из 1 битов, которые установлены в числе k, вы получите меньшее число (позвоните ему m), а количество бит, установленное в k, будет равно единице плюс число бит, установленных в м. Таким образом, при условии, что мы можем найти способ удалить любой 1 бит из числа k, мы можем использовать этот шаблон для решения проблемы:
result[0] = 0 // No bits set in 0
for k = 1 to n:
let m = k, with some 1 bit cleared
result[k] = result[m] + 1
Там знаменитый бит-трюк, где
k & (k - 1)
дает число, сформированное путем сброса младшего 1 бита, установленного в числе k, и делает это во времени O (1), считая, что машина может выполнять побитовые операции в постоянное время (что обычно является разумным предположением). Это означает, что этот псевдокод должен сделать это:
result[0] = 0
for k = 1 to n:
result[k] = result[k & (k - 1)] + 1
Это означает, что O (1) работает на число O (n) общего времени, поэтому общая работа выполнена O (n).
Здесь другой способ сделать это. Представьте себе, например, что вы знаете количество бит в числах 0, 1, 2 и 3. Вы можете использовать это для генерации счетчиков битов чисел 4, 5, 6 и 7, заметив, что эти числа имеют побитовые представления, которые формируются путем приема побитовых представлений 0, 1, 2 и 3, а затем добавление 1. Аналогичным образом, если вы тогда знали количество бит 0, 1, 2, 3, 4, 5, 6 и 7, вы можете сгенерировать количество бит 8, 9, 10, 11, 12, 13, 14 и 15, отметив, что они тоже сформированы путем добавления 1 бит к каждому из младших чисел. Это порождает этот псевдокод, который для простоты предполагает, что n имеет вид 2 k - 1, но может быть легко адаптирован для общего n:
result[0] = 0;
for (int powerOfTwo = 1; powerOfTwo < n; powerOfTwo *= 2) {
for (int i = 0; i < powerOfTwo; i++) {
result[powerOfTwo + i] = result[i] + 1;
}
}
Это также выполняется во времени O (n). Чтобы увидеть это, обратите внимание, что на всех итерациях всех циклов здесь каждая запись в массиве записывается ровно один раз, а O (1) выполняется, чтобы определить, какое значение должно быть помещено в массив в этом слоте.