Підрахуйте число 1 в двійковому поданні


77

Ефективний спосіб підрахувати кількість 1s у двійковому поданні числа в O (1), якщо у вас є достатньо пам’яті для гри. Це запитання для інтерв’ю, яке я знайшов на форумі в Інтернеті, але на нього не було відповіді. Хтось може щось підказати, я не можу придумати, як це зробити за час O (1)?


2
Тож питання вимагає, щоб ви обдурили - «достатньої» пам’яті могло б бути більше бітів, ніж атомів у спостережуваному Всесвіті.
harold

Це не просто масив довжиною MAX_INT?
Борис Треухов

1
будь ласка, виправте мене - якщо у нас є масив [0..MAX_INT-1], де індекс - це фактичне число на вході, а дані - це число 1 для цього числа (і припустимо, це реалізовано через адресувану до вмісту пам’ять en .wikipedia.org / wiki / Content-addressable_memory ) чи не буде це O (1)?
Борис Треухов

2
Це, мабуть, рішення для інтерв'ю з моделями, хоча я не думаю, що воно задовольнить пуриста, оскільки воно обмежене шириною даних адресованої пам'яті на машині (скажімо, 64 біт). Це не буде працювати для чисел> 2 ^ 64, і, як зазначено, питання не накладає цього обмеження. Якщо питання було переглянуто, щоб сказати "64-бітове число", тоді так, це хороше рішення.
Tim Gee

З іншого боку, для 64-розрядних чисел старі методи підрахунку бітів також мають значення O (1) і майже не використовують пам’ять.
Даніель Фішер,

Відповіді:


57

Це проблема ваги Хеммінга , вона ж кількість населення. Посилання згадує про ефективні реалізації. Цитування:

Маючи необмежену пам’ять, ми могли просто створити велику таблицю пошуку ваги Хеммінга кожного 64-бітного цілого числа


8
+1 за правильне називання цієї проблеми. Хоча я думаю, що повна відповідь стверджує, що навіть використання таблиці пошуку не буде часом O (1), оскільки час пошуку будь-якого запису залежить від розміру запису.
Tim Gee

@TimGee Доступ LUT завжди розглядається як O (1). Коли у вас необмежена пам’ять, у вас також є необмежена шина адрес. Тож до якої адреси (= вводу) ви не звертаєтесь, це завжди лише один доступ до пам'яті ;-)
Сколіт

@Scolytus, будь-які посилання на "Коли у вас необмежена пам'ять, у вас також є необмежена шина адрес."?
sasha.sochka

1
@ sasha.sochka необмежена пам’ять - теоретична конструкція. У нього взагалі немає адресної шини, це лише припущення, що у вас завжди достатньо пам’яті і ви завжди можете отримати до неї однаковий доступ, незалежно від її розміру. Отже, у реальній реалізації необмеженої пам'яті ваша адресна шина завжди буде> log2 (count (addressable_memory_units)).
Сколіт

1
@JackAidley де посилання?
Оскар Лопес

49

У мене є рішення, яке підраховує біти в O(Number of 1's)часі:

bitcount(n):
    count = 0
    while n > 0:
        count = count + 1
        n = n & (n-1)
    return count

У гіршому випадку (коли число 2 ^ n - 1, усі одиниці в двійковому вигляді) він буде перевіряти кожен біт.

Редагувати: Щойно знайшов дуже приємний алгоритм постійної пам’яті, постійної пам’яті для кількості бітів. Ось воно, написане на C:

int BitCount(unsigned int u)
{
     unsigned int uCount;

     uCount = u - ((u >> 1) & 033333333333) - ((u >> 2) & 011111111111);
     return ((uCount + (uCount >> 3)) & 030707070707) % 63;
}

Доказ його правильності ви можете знайти тут .


Ідея питання полягає у використанні простого часу, тобто ніякої різниці в тривалості, чи є нуль, одиниця чи багато одиниць.
Владислав Зоров

1
Другий - це лише постійний час, оскільки розмір вхідних даних є постійним, і в такому випадку такий же перший. Перший алгоритм - це насправді O (log n) в загальному випадку через побітове І та віднімання, яке триває так довго.
harold

4
Ваша перша відповідь - O (log n), а не O (1). Ваша друга відповідь - O (1), але передбачає домен у 32 біти (вхідним параметром є unsigned int).
Stephen Quan

Власне, нам слід вказати, що таке n. Для задачі 1: Якщо n - кількість бітів у вихідному номері, то це O (n * число 1s), тобто O (n ^ 2). Якщо n - значення числа, то це O (lg ^ 2 n).
Джон Курлак

1
Якщо хтось із вас задається питанням, що робить n = n & (n-1), це очищає найменш значущий встановлений біт (1) n. Отже, ефективно, коли n = 0, ми прокручували час відповіді всередині.
Ε Г И І І О О

18

Зверніть увагу на той факт, що: n & (n-1) завжди виключає найменш значущу 1.

Звідси ми можемо написати код для обчислення числа 1 наступним чином:

count=0;
while(n!=0){
  n = n&(n-1);
  count++;
}
cout<<"Number of 1's in n is: "<<count;

Складність програми буде такою: число одиниць в n (що постійно <32).


16

Я побачив таке рішення з іншого веб-сайту:

int count_one(int x){
    x = (x & (0x55555555)) + ((x >> 1) & (0x55555555));
    x = (x & (0x33333333)) + ((x >> 2) & (0x33333333));
    x = (x & (0x0f0f0f0f)) + ((x >> 4) & (0x0f0f0f0f));
    x = (x & (0x00ff00ff)) + ((x >> 8) & (0x00ff00ff));
    x = (x & (0x0000ffff)) + ((x >> 16) & (0x0000ffff));
    return x;
}

10
public static void main(String[] args) {

    int a = 3;
    int orig = a;
    int count = 0;
    while(a>0)
    {
        a = a >> 1 << 1;
        if(orig-a==1)
            count++;
        orig = a >> 1;
        a = orig;
    }

    System.out.println("Number of 1s are: "+count);
}

4
+1 Хороша відповідь. Спробуйте додати якесь пояснення. Особливо для деталей, що зміщують долота в обидві сторони, що для деяких може заплутати.
бримборій

1
Пояснення: A) Якщо найменший значущий біт дорівнює 1, збільште лічильник на 1 (ось як працює ця частина: зміщення вправо, а потім скасування зсуву встановлює найменший значущий біт на 0 ... якщо різниця між старим числом та новим числом дорівнює 1, тоді a 1 було видалено) B) Ділимо число на 2 і беремо слово, зміщуючи вправо 1 C) Повторюємо, поки число не дорівнює 0. Було б простіше просто І число з 1 визначити, чи є найменш значущою цифрою 1.
Джон Курлак

3
Привіт, мені було просто цікаво, чи не було if (orig & 1) count++; orig >>= 1;б трохи ефективніше?
Heartinpiece

2
Або ще краще:count += orig & 1; orig >>= 1;
Ной,


4

Це буде найкоротша відповідь у моєму житті SO: таблиця пошуку.

Очевидно, мені потрібно трохи пояснити: "якщо у вас достатньо пам'яті, з якою можна пограти", означає, що у нас є вся потрібна пам'ять (не зважаючи на технічну можливість). Тепер вам не потрібно зберігати таблицю пошуку більше, ніж байт або два. Хоча технічно це буде Ω (log (n)), а не O (1), просто зчитування потрібного вам числа є Ω (log (n)), тож якщо це проблема, то відповідь неможлива - що ще коротше.

Якої з двох відповідей вони чекають від вас на співбесіді, ніхто не знає.

Існує ще одна хитрість: в той час як інженери можуть взяти число і поговорити про Ω (log (n)), де n - це число, вчені-комп’ютерники скажуть, що насправді ми повинні вимірювати час роботи як функцію довжини введення , отже, те, що інженери називають Ω (log (n)), насправді є Ω (k), де k - кількість байтів. І все-таки, як я вже говорив раніше, просто читання числа дорівнює Ω (k), тож ми не можемо зробити краще цього.


А що відбувається, коли ми маємо справу з 64-розрядними числами?
леппі

1
"якщо у вас достатньо пам’яті для гри" для початку. Тепер вам не потрібно зберігати таблицю пошуку більше, ніж байт або два (так, я знаю, що технічно це буде O (log (n)), а не O (1), але тоді просто прочитати потрібне вам число O (log (n))).
alf

Я думаю, ви можете прочитати число в O (1) з апаратним паралелізмом, але ви все одно застрягли в O (log (k)), коли вам потрібно прийняти рішення щодо цього числа (будь то таблиця пошуку або часткова сума операція).
Tim Gee

2

Нижче також буде працювати.

nofone(int x) {
  a=0;
  while(x!=0) {
    x>>=1;
    if(x & 1)
      a++;
  }
  return a;
} 

1
Насправді в цьому коді є тривіальна помилка. Щоб побачити це, просто уявіть, що відбувається при х = 1.
Андреас Рейбранд,

2

Нижче наведено рішення C із використанням бітових операторів:

int numberOfOneBitsInInteger(int input) {
  int numOneBits = 0;

  int currNum = input;
  while (currNum != 0) {
    if ((currNum & 1) == 1) {
      numOneBits++;
    }
    currNum = currNum >> 1;
  }
  return numOneBits;
}

Далі подано рішення Java, що використовує повноваження 2:

public static int numOnesInBinary(int n) {

  if (n < 0) return -1;

  int j = 0;
  while ( n > Math.pow(2, j)) j++;

  int result = 0;
  for (int i=j; i >=0; i--){
    if (n >= Math.pow(2, i)) {
        n = (int) (n - Math.pow(2,i));
        result++;    
    }
  }

  return result;
}

2

Нижче наведено два простих приклади (на C ++), серед яких ви можете це зробити.

  1. Ми можемо просто порахувати встановлені біти (1) за допомогою __builtin_popcount ().

    int numOfOnes(int x) { return __builtin_popcount(x); }

  2. Прокрутіть усі біти у цілому числі, перевірте, чи встановлений біт, і якщо він потім, збільште змінну count.

    int hammingDistance(int x) { int count = 0 for(int i = 0; i < 32; i++) if(x & (1 << i)) count++; return count; }

Сподіваюся, це допомагає!


1

Функція приймає intі повертає кількість Онів у двійковому поданні

public static int findOnes(int number)
{

   if(number < 2)
    {
        if(number == 1)
        {
            count ++;
        }
        else
        {
            return 0;
        }
    }

    value = number % 2;

    if(number != 1 && value == 1)
        count ++;

    number /= 2;

    findOnes(number);

    return count;
}

1

Найкращий спосіб у javascript це зробити

function getBinaryValue(num){
 return num.toString(2);
}

function checkOnces(binaryValue){
    return binaryValue.toString().replace(/0/g, "").length;
}

де binaryValue - це двійковий рядок, наприклад: 1100


0

Існує лише один спосіб, яким я можу придумати виконати це завдання в O (1) ... це "обдурити" і використовувати фізичний пристрій (при лінійному або навіть паралельному програмуванні, на мою думку, обмеження - O (log (k)) де k являє собою кількість байтів числа).

Однак ви могли б дуже легко уявити фізичний пристрій, який з'єднує кожен біт і вихідну лінію напругою 0/1. Тоді ви могли просто прочитати в електронній мережі загальну напругу на лінії 'підсумовування' в O (1). Було б досить легко зробити цю базову ідею більш елегантною за допомогою деяких основних елементів схеми, щоб отримати вихід у будь-якій формі, яку ви хочете (наприклад, двійково закодований вихід), але основна ідея та сама, і електронна схема дасть правильний результат стан у встановлений час.

Я гадаю, є також можливі квантові обчислення, але якщо нам це дозволено, я думаю, простіші електронні схеми є простішим рішенням.


0

Я насправді зробив це, використовуючи трохи спритності: достатньо однієї таблиці пошуку з 16 записами, і все, що вам потрібно зробити, це розбити двійковий реп на ключки (4-бітні кортежі). Складність насправді O (1), і я написав шаблон C ++, який спеціалізувався на розмірі цілого числа, яке ви хотіли (у # ​​бітах) ... робить його постійним виразом, а не невизначеним.

fwiw ви можете використовувати той факт, що (i & -i) поверне вам LS однобітний і просто цикл, щоразу знімаючи lsbit, доки ціле число не буде нульовим - але це стара хитрість паритету.


Ваша відповідь O (1) передбачає, що домен є 64-розрядним.
Stephen Quan

Чому б не використовувати 16-розрядну таблицю пошуку? Використання 64 КБ пам’яті було б практично непоміченим на більшості комп’ютерів сьогодні, і для підрахунку бітів 64-бітового цілого числа потрібно лише чотири пошуки.
Kef Schecter

0

Я прийшов сюди, переконавшись, що знаю прекрасне рішення цієї проблеми. Код на C:

    short numberOfOnes(unsigned int d) {
        short count = 0;

        for (; (d != 0); d &= (d - 1))
            ++count;

        return count;
    }

Але після невеликого дослідження на цю тему (прочитайте інші відповіді :)) я знайшов 5 більш ефективних алгоритмів. Любіть ТАК!

Існує навіть інструкція CPU розроблений спеціально для вирішення цього завдання: popcnt. (згадане у цій відповіді )

Опис та порівняльний аналіз багатьох алгоритмів ви можете знайти тут .


0

Наведений нижче метод також може підрахувати кількість одиниць у від’ємних числах.

private static int countBits(int number)    {
    int result = 0;
    while(number != 0)  {
        result += number & 1;
        number = number >>> 1;
    }
    return result;
}

Однак число, подібне до -1, представлене у двійковому вигляді як 1111111111111111111111111111111111, і тому буде потрібно багато змін. Якщо ви не хочете робити стільки змін для малих від’ємних чисел, іншим способом може бути такий:

private static int countBits(int number)    {
    boolean negFlag = false;
    if(number < 0)  { 
        negFlag = true;
        number = ~number;
    }

    int result = 0;
    while(number != 0)  {
        result += number & 1;
        number = number >> 1;
    }
    return negFlag? (32-result): result;
}

0

У python або будь-якому іншому перетворенні в рядок bin, потім розділіть його на "0", щоб позбутися нулів, потім об'єднайте і отримайте довжину.

len(''.join(str(bin(122011)).split('0')))-1

2
У python чому б і ні bin(122011).count("1")?
юстенгель

0

Використовуючи рядкові операції JS, можна зробити наступне;

0b1111011.toString(2).split(/0|(?=.)/).length // returns 6

або

0b1111011.toString(2).replace("0","").length  // returns 6

0

Мені довелося гольфувати це в рубіні, і в підсумку

l=->x{x.to_s(2).count ?1}

Використання:

l[2**32-1] # returns 32

Очевидно, неефективний, але робить фокус :)


0

Впровадження Ruby

def find_consecutive_1(n)
  num = n.to_s(2)
  arr = num.split("")
  counter = 0
  max = 0
  arr.each do |x|
      if x.to_i==1
          counter +=1
      else
          max = counter if counter > max
          counter = 0 
      end
      max = counter if counter > max  
  end
  max
end

puts find_consecutive_1(439)

0

Два шляхи ::

/* Method-1 */
int count1s(long num)
{
    int tempCount = 0;

    while(num)
    {
        tempCount += (num & 1); //inc, based on right most bit checked
        num = num >> 1;         //right shift bit by 1
    }

    return tempCount;
}

/* Method-2 */
int count1s_(int num)
{
    int tempCount = 0;

    std::string strNum = std::bitset< 16 >( num ).to_string(); // string conversion
    cout << "strNum=" << strNum << endl;
    for(int i=0; i<strNum.size(); i++)
    {
        if('1' == strNum[i])
        {
            tempCount++;
        }
    }

    return tempCount;
}

/* Method-3 (algorithmically - boost string split could be used) */
1) split the binary string over '1'.
2) count = vector (containing splits) size - 1

Використання ::

    int count = 0;

    count = count1s(0b00110011);
    cout << "count(0b00110011) = " << count << endl; //4

    count = count1s(0b01110110);
    cout << "count(0b01110110) = " << count << endl;  //5

    count = count1s(0b00000000);
    cout << "count(0b00000000) = " << count << endl;  //0

    count = count1s(0b11111111);
    cout << "count(0b11111111) = " << count << endl;  //8

    count = count1s_(0b1100);
    cout << "count(0b1100) = " << count << endl;  //2

    count = count1s_(0b11111111);
    cout << "count(0b11111111) = " << count << endl;  //8

    count = count1s_(0b0);
    cout << "count(0b0) = " << count << endl;  //0

    count = count1s_(0b1);
    cout << "count(0b1) = " << count << endl;  //1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.