Створіть ціле число, яке не належить до чотирьох мільярдів заданих


691

Мені було дано це питання інтерв'ю:

Дано вхідний файл з чотирма мільярдами цілих чисел, надайте алгоритм для створення цілого числа, яке не міститься у файлі. Припустимо, у вас є 1 ГБ пам'яті. Слідкуйте за тим, що б ви зробили, якщо у вас є лише 10 МБ пам'яті.

Мій аналіз:

Розмір файлу - 4 × 10 9 × 4 байти = 16 ГБ.

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

Моє запитання - який найкращий спосіб виявити відсутнє ціле число у відсортованих великих цілих наборах?

Моє розуміння (прочитавши всі відповіді):

Припустимо, що ми говоримо про 32-бітні цілі числа, існує 2 32 = 4 * 10 9 різних цілих чисел.

Випадок 1: у нас є 1 ГБ = 1 * 10 9 * 8 біт = 8 мільярдів біт пам'яті.

Рішення:

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

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

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

Випадок 2: 10 Мб пам'яті = 10 * 10 6 * 8 біт = 80 мільйонів біт

Рішення:

Для всіх можливих 16-бітових префіксів існує 2 16 число цілих чисел = 65536, нам потрібно 2 16 * 4 * 8 = 2 мільйони біт. Нам потрібно зібрати 65536 відра. Для кожного відра нам потрібно 4 байти, що містять усі можливості, оскільки найгірший випадок, коли всі 4 мільярди цілих чисел належать одному і тому ж відро.

  1. Побудуйте лічильник кожного відра через перший прохід через файл.
  2. Проскануйте відра, знайдіть першого, у якого менше 65536 влучень.
  3. Створіть нові відра, високі 16-бітні префікси яких ми знайшли в кроці 2 через другий прохід файлу
  4. Скануйте відра, вбудовані в step3, знайдіть перше відро, яке не має хіта.

Код дуже схожий на вищезгаданий.

Висновок: Ми зменшуємо пам’ять за рахунок збільшення пропуску файлів.


Пояснення для тих, хто приходить із запізненням: Запитане запитання не говорить про те, що існує точно одне ціле число, яке не міститься у файлі - принаймні, так не трактує це більшість людей. Багато коментарі в коментар нитка є про те , що зміни завдання, хоча. На жаль, коментар, який вніс його до нитки коментарів, пізніше був видалений його автором, тому зараз схоже, що сиротні відповіді на нього просто неправильно зрозуміли все. Це дуже заплутано, вибачте.


32
@trashgod, неправильно. Для 4294967295 унікальних цілих чисел у вас залишиться 1 ціле число. Щоб знайти його, слід підсумовувати всі цілі числа та вилучати їх з попередньо обчисленого суми всіх можливих цілих чисел.
Накілон

58
Це друга "перлина" від "Перлини програмування", і я б запропонував вам прочитати всю дискусію в книзі. Дивіться books.google.com/…
Алок Сінгал

8
@Richard 64-бітний інт був би більш ніж великий.
cftarnas

79
int getMissingNumber(File inputFile) { return 4; }( довідник )
johnny

14
Не має значення, що ви не можете зберігати суму всіх цілих чисел від 1 до 2 ^ 32, оскільки цілочисельний тип у таких мовах, як C / C ++ ЗАВЖДИ зберігає такі властивості, як асоціативність та комунікативність. Це означає, що хоча сума не буде правильною відповіддю, якщо обчислити очікуване з переливом, фактичну суму з переливом, а потім відняти, результат все одно буде правильним (за умови, що він сам не переливається).
thedayturns

Відповіді:


530

Якщо припустити, що "ціле число" означає 32 біта : 10 Мб простору вам більш ніж достатньо, щоб порахувати, скільки чисел є у вхідному файлі з будь-яким 16-бітовим префіксом, для всіх можливих 16-бітових префіксів за один прохід через вхідний файл. Принаймні одне з відра буде вдарено менше 2 16 разів. Зробіть другий прохід, щоб знайти, яке з можливих чисел у цьому відрі вже використовується.

Якщо це означає більше 32 біт, але все-таки обмеженого розміру : Зробіть так, як зазначено вище, ігноруючи всі вхідні номери, які трапляються за межами (підписаного або без підпису; ваш вибір) 32-бітного діапазону.

Якщо "ціле число" означає математичне ціле число : прочитайте один раз вхід і відстежте найбільшу довжину числа найдовшого числа, яке ви коли-небудь бачили. Коли ви закінчите, виведіть максимум плюс одне випадкове число, яке має ще одну цифру. (Одне з чисел у файлі може бути бінтумом, який займає більше 10 Мб, щоб точно зобразити, але якщо вхід є файлом, то ви можете принаймні представити довжину всього, що в ньому вміщується).


24
Ідеально. Ваша перша відповідь вимагає всього 2 пропуску через файл!
corsiKa

47
Бігнум 10 Мб? Це досить екстремально.
Марк Викуп

12
@Legate, просто пропустіть великі номери і нічого не робите з ними. Оскільки ви все одно не збираєтеся виводити номер надмірного розміру, не потрібно слідкувати за тим, який з них ви бачили.
hmakholm залишився над Монікою

12
Хороша річ у рішенні 1, полягає в тому, що ви можете зменшити пам'ять, збільшуючи пропуск.
Yousf

11
@Barry: Питання вище не вказує на відсутність рівно одного номера. Це не говорить, що цифри у файлі також не повторюються. (Після фактично
заданого

197

Статистично проінформовані алгоритми вирішують цю проблему, використовуючи менше пропусків, ніж детерміновані підходи.

Якщо дозволено дуже великі цілі числа, то можна створити число, яке, ймовірно, буде унікальним за час O (1). Псевдовипадкове 128-бітове ціле число, подібне до GUID , зіткнеться лише з одним із існуючих чотирьох мільярдів цілих чисел у наборі менше ніж одне з кожні 64 мільярди мільярдів випадків.

Якщо цілі числа обмежені 32 бітами, то можна створити число, яке, ймовірно, буде унікальним за один прохід, використовуючи набагато менше 10 МБ. Шанси на те, що псевдовипадкове 32-бітове ціле число зіткнеться з одним із 4 мільярдів існуючих цілих чисел, становить близько 93% (4e9 / 2 ^ 32). Шанси, що 1000 псевдовипадкових цілих чисел зіткнуться, менше одного на 12000 мільярдів мільярдів (шанси на зіткнення ^ 1000). Отже, якщо програма підтримує структуру даних, що містить 1000 псевдовипадкових кандидатів, і повторює відомі цілі числа, виключаючи відповідність кандидатів, то, окрім певного, можна знайти хоча б одне ціле число, яке не міститься у файлі.


32
Я майже впевнений, що цілі числа обмежені. Якби їх не було, тоді навіть програміст-початківець подумав би алгоритм «пройди один прохід через дані, щоб знайти максимальну кількість, і додати до нього 1»
Адріан Петреску,

12
Буквально здогадуючись про випадковий вихід, напевно, не ви отримаєте багато балів на інтерв'ю
Брайан Гордон

6
@ Adrian, ваше рішення здається очевидним (і це було для мене, я використовував це у власній відповіді), але це не очевидно для всіх. Це хороший тест, щоб дізнатись, чи зможете ви помітити очевидні рішення чи збираєтесь надмірно ускладнювати все, до чого торкаєтесь.
Марк Викуп

19
@Brian: Я думаю, що це рішення є і уявним, і практичним. Я за один би дав багато кудо за цю відповідь.
Річард Н

6
ах тут лежить межа між інженерами та вченими. Чудова відповідь Бен!
TrojanName

142

Детальна дискусія з цієї проблеми була обговорена в Джоні Бентлі "Колона 1. Розтріскування устриці" Програмування Перлів Аддісон-Уеслі с.3-10

Bentley обговорює декілька підходів, включаючи зовнішнє сортування, Merge Sort з використанням декількох зовнішніх файлів тощо. Але найкращий метод, який пропонує Bentley, - це алгоритм одноразового проходження з використанням бітових полів , який він жартівливо називає "Wonder Sort" :) Що стосується проблеми, 4 мільярди числа можуть бути представлені у:

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

Код для реалізації бітсету простий: (взято зі сторінки рішень )

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Алгоритм Бентлі робить один прохід по файлу, setпідкреслюючи відповідний біт у масиві, а потім вивчає цей масив, використовуючи testмакрос вище, щоб знайти відсутнє число.

Якщо наявна пам'ять менше 0,466 Гб, Bentley пропонує алгоритм k-pass, який розділяє вхід на діапазони залежно від наявної пам'яті. Візьмемо дуже простий приклад, якщо було доступно лише 1 байт (тобто пам'ять для обробки 8 чисел) і діапазон від 0 до 31, ми поділимо це на діапазони від 0 до 7, 8-15, 16-22 і так далі і обробляти цей діапазон у кожному з 32/8 = 4проходів.

HTH.


12
Я не знаю книги, але немає причин називати її "Чудовим сортуванням", оскільки це просто ковша, з 1-бітним лічильником.
flolo

3
Хоча більш портативний, цей код буде знищений кодом, написаним для використання векторних інструкцій, підтримуваних апаратним забезпеченням . Я думаю, що gcc може автоматично перетворювати код на використання векторних операцій в деяких випадках.
Брайан Гордон

3
@brian Я не думаю, що Джон Бентлі допускав такі речі до своєї книги про алгоритми.
Девід Геффернан

8
@BrianGordon, час, проведений в операційній пам'яті, буде незначним порівняно з часом, витраченим на читання файлу. Забудьте про її оптимізацію.
Ян

1
@BrianGordon: Або ви говорили про цикл наприкінці, щоб знайти перший невстановлений біт? Так, вектори прискорять це, але перебираючи бітове поле з 64-бітовими цілими числами, шукаючи таке, яке != -1все одно наситить пропускну здатність пам’яті, що працює на одному ядрі (це SIMD-в-реєстрі, SWAR, з бітами як елементами). (Для останніх конструкцій Intel / AMD). Ви повинні розібратися, який біт не налаштований після того, як ви знайдете 64-бітове розташування, що містить його. (І для цього ви можете not / lzcnt.) Справедлива думка, що циклічність однобітного тесту може не бути оптимізованою.
Пітер Кордес

120

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


6
Якщо найбільше число у файлі не буде max int, ви просто переповниться
KBusc

Який би розмір був цей файл у реальній програмі Світу, якій, можливо, знадобиться генерувати нове ціле число та додавати його до файлу "використані цілі числа" у 100 разів?
Михайло

2
Я думав про це. Якщо припустити, intце 32біти, просто вихід 2^64-1. Зроблено.
imallett

1
Якщо це один int на рядок tr -d '\n' < nums.txt > new_num.txt:: D
Шоно

56

Для 1 Гб оперативної пам’яті можна використовувати бітовий вектор. Вам потрібно виділити 4 мільярди біт == 500 Мб байтового масиву. Для кожного числа, яке ви читаєте з введення, встановіть відповідний біт на "1". Після цього перейдіть за бітами, знайдіть перший, який ще є "0". Його індекс - відповідь.


4
Діапазон чисел у введенні не вказаний. Як працює цей алгоритм, якщо вхід складається з парних чисел від 8 до 16 мільярдів?
Марк Рансом

27
@ Марк, просто ігноруйте входи, які знаходяться за межами діапазону 0..2 ^ 32. Ви все одно не збираєтесь виводити жодну з них, тому не потрібно пам’ятати, кого з них уникати.
hmakholm виїхав над Монікою

@ Відмітьте алгоритм, який ви використовуєте, щоб визначити, як 32-бітна рядок відображає реальне число. Процес все одно той самий. Різниця полягає лише в тому, як ви друкуєте його як реальне число на екрані.
corsiKa

4
Замість того, щоб ітератувати себе, ви можете скористатися bitSet.nextClearBit(0): download.oracle.com/javase/6/docs/api/java/util/…
starblue

3
Було б корисно згадати, що незалежно від діапазону цілих чисел, принаймні один біт гарантовано дорівнює 0 в кінці проходу. Це пов’язано з принципом голуби.
Rafał Dowgird

46

Якщо вони є 32-бітовими цілими числами (ймовірно, вибирати ~ 4 мільярди чисел, близьких до 2 32 ), ваш список із 4 мільярдів чисел займе не більше 93% від можливих цілих чисел (4 * 10 9 / (2 32 ) ). Отже, якщо ви створюєте бітовий масив з 2 32 біт з кожним бітом, ініціалізованим до нуля (що займе 2 29 байт ~ 500 Мб оперативної пам’яті; запам’ятайте байт = 2 3 біта = 8 біт), прочитайте свій цілий список і для кожного int встановити відповідний елемент бітового масиву від 0 до 1; а потім прочитайте ваш бітовий масив і поверніть перший біт, який все ще дорівнює 0.

У випадку, коли у вас менше оперативної пам’яті (~ 10 Мб), це рішення потрібно трохи змінити. 10 Мб ~ 83886080 біт все ще достатньо, щоб зробити бітовий масив для всіх чисел від 0 до 83886079. Таким чином, ви могли прочитати список списку ints; і записуйте лише #, які знаходяться між 0 і 83886079 у вашому бітовому масиві. Якщо числа розподілені випадковим чином; з переважною ймовірністю (вона відрізняється на 100% приблизно на 10 -2592069 ) ви знайдете відсутній int). Насправді, якщо ви виберете лише цифри від 1 до 2048 (із лише 256 байтами оперативної пам’яті), ви все одно знайдете пропущене число, переважний відсоток (99,9999999999999999999999999999999999999999999999999999999999999999999995995%) того часу.

Але скажімо, замість того, щоб мати близько 4 мільярдів чисел; у вас було щось на кшталт 2 32 - 1 числа і менше 10 Мб оперативної пам’яті; тому будь-який невеликий діапазон входів має лише невелику можливість не містити числа.

Якщо вам було гарантовано, що кожен int у списку є унікальним, ви можете підсумувати числа та відняти суму з одного # пропущеного до повної суми (½) (2 32 ) (2 32 - 1) = 9223372034707292160, щоб знайти відсутній int . Однак якщо інт стався двічі, цей метод не вдасться.

Однак завжди можна розділити і підкорити. Наївним методом було б прочитати масив і порахувати кількість чисел, які знаходяться в першій половині (0 до 2 31 -1) і в другій половині (2 31 , 2 32 ). Потім виберіть діапазон з меншим числом і повторіть ділення цього діапазону навпіл. (Скажіть, якщо в (2 31 , 2 32 ) було два менших числа , то при наступному пошуку буде підраховано числа в діапазоні (2 31 , 3 * 2 30 -1), (3 * 2 30 , 2 32 ). повторюючись, поки ви не знайдете діапазон з нульовими числами, і ви отримаєте свою відповідь. Повинен взяти O (lg N) ~ 32 зчитування через масив.

Цей метод виявився неефективним. Ми використовуємо лише два цілих числа на кожному кроці (або приблизно 8 байт оперативної пам’яті з 4-байтовим (32-бітовим) цілим числом). Кращим методом було б поділити на sqrt (2 32 ) = 2 16 = 65536 бін, кожен з 65536 числами у відро. Кожен бін вимагає 4 байти, щоб зберігати його кількість, тому вам потрібно 2 18 байт = 256 кБ. Отже, бін 0 дорівнює (0 до 65535 = 2 16 -1), бін 1 дорівнює (2 16 = 65536 до 2 * 2 16 -1 = 131071), бін 2 дорівнює (2 * 2 16 = 131072 до 3 * 2 16 - 1 = 196607). У python у вас є щось на кшталт:

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

Прочитайте ~ 4 мільярди цілого списку; і порахуйте, скільки входів потрапляє в кожну з 2 16 бункерів і знайдіть неповний_бін, який не має всіх 65536 номерів. Потім ви знову прочитаєте цілий список із 4 мільярдів; але цього разу лише помічають, коли цілі числа знаходяться в цьому діапазоні; трохи гортаючи, коли ви їх знайдете.

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break

3
Така дивовижна відповідь. Це фактично спрацювало б; і має гарантовані результати.
Джонатан Дікінсон

@dr jimbob, що робити, якщо у кошику є лише одне число, і це одне число має 65535 дублікатів? Якщо так, то кошик все ще налічує 65536, але всі 65536 номери однакові.
Алькотт

@Alcott - Я припускав, що у вас є 2 ^ 32-1 (або менше) номерів, тож за принципом «голубого отвору» ви гарантовано матимете один відро з меншим числом 65536, щоб перевірити більш детально. Ми намагаємось знайти лише одне відсутнє ціле число, не всі. Якщо у вас було 2 ^ 32 або більше чисел, ви не можете гарантувати відсутнє ціле число і не зможете скористатися цим методом (або з самого початку маєте гарантію на відсутність цілого числа). Вашою найкращою ставкою тоді була б груба сила (наприклад, прочитати масив 32 рази; перевірити перший 65536 # з першого разу; і зупинити, коли відповідь знайдена).
д-р Джимбоб

Розумний метод верхній 16 / нижній-16 був опублікований раніше Henning: stackoverflow.com/a/7153822/224132 . Мені сподобалася ідея надбудови для унікального набору цілих чисел, у яких відсутня рівно один член.
Пітер Кордес

3
@PeterCordes - Так, рішення Геннінга передує моєму, але я вважаю, що моя відповідь все ще корисна (детальніше розглядаю декілька речей). При цьому Джон Бентлі у своїй книзі «Програмування перлів» запропонував багатопропускний варіант для цієї проблеми (див. Відповідь виноградної лози) способом до існування stackoverflow (не те, що я стверджую, що хтось із нас свідомо вкрав звідти, або що Бентлі був першим проаналізуйте цю проблему - це досить природне рішення, яке потрібно розвивати). Два проходи здаються найбільш природними, коли обмеженням у вас більше не вистачає пам'яті для рішення з 1 пропусканням з гігантським бітовим масивом.
д-р Джимбоб

37

Чому це зробити так складно? Ви запитуєте ціле число, яке немає у файлі?

Відповідно до вказаних правил, єдине, що вам потрібно зберегти - це найбільше ціле число, з яким ви стикалися до цього часу у файлі. Після того, як весь файл буде прочитаний, поверніть номер 1, більший за нього.

Немає ризику потрапляння на maxint чи що-небудь, тому що згідно з правилами немає обмеження на величину цілого чи числа, повернутого алгоритмом.


4
Це спрацювало б, якщо у файлі не було max int, що цілком можливо ...
PearsonArtPhoto

13
У правилах не вказано, що це 32-бітний або 64-бітний або що-небудь інше, тому згідно з вказаними правилами не існує макс. Ціле число не є комп’ютерним терміном, це математичний термін, що ідентифікує позитивні чи негативні цілі числа.
Піт

Щоправда, але не можна припустити, що це 64-бітове число або що хтось не просто прокрадеться в максимальне число int, щоб просто переплутати такі алгоритми.
PearsonArtPhoto

24
Все поняття "max int" недійсне в контексті, якщо не вказана мова програмування. наприклад, подивіться на визначення Python великого цілого числа. Це безмежно. Даху немає. Ви завжди можете додати його. Ви припускаєте, що він реалізований мовою, яка має максимально дозволене значення для цілого числа.
Піт

32

Це можна вирішити за дуже мало місця, використовуючи варіант двійкового пошуку.

  1. Почніть з дозволеного діапазону чисел, 0до 4294967295.

  2. Обчисліть середину.

  3. Проведіть файл через підрахунок, скільки чисел було рівним, меншим або більшим за значення середньої точки.

  4. Якщо жодне число не було рівним, ви закінчите. Середня точка - це відповідь.

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

Для цього знадобиться до 32 лінійних сканів через файл, але він буде використовувати лише кілька байт пам'яті для зберігання діапазону та підрахунків.

Це по суті те саме, що і рішення Хеннінга , за винятком того, що він використовує два бункери замість 16k.


2
З цього я почав ще до того, як почав оптимізувати задані параметри.
hmakholm залишився над Монікою

@Henning: Класно. Це приємний приклад алгоритму, коли легко налаштувати проміжок часу та часу.
hammar

@hammar, але що робити, якщо є ті цифри, які з’являються не один раз?
Алькотт

@Alcott: тоді алгоритм буде вибирати щільніший бункер замість більш рідкого відро, але за принципом голубого отвору він ніколи не може вибрати повністю повний бункер. (Менший з двох рахунків завжди буде меншим, ніж діапазон сміття.)
Пітер Кордес

27

EDIT Добре, це було не зовсім продумано, оскільки передбачає, що цілі числа у файлі дотримуються деякого статичного розподілу. Мабуть, їм не потрібно, але навіть тоді слід спробувати це:


Існує ≈4,3 мільярда 32-бітних цілих чисел. Ми не знаємо, як вони розподіляються у файлі, але найгірший випадок - це той, який має найвищу ентропію Шеннона: рівне розподіл. У цьому випадку ймовірність того, що якесь одне ціле число не відбудеться у файлі, є

((2³²-1) / 2³²) ⁴ ⁰⁰⁰ ⁰⁰⁰ ⁰⁰⁰ ≈ .4

Чим менша ентропія Шеннона, тим вища ця ймовірність отримує в середньому, але навіть у цьому найгіршому випадку ми маємо шанс на 90% знайти неточне число після 5 здогадок із випадковими цілими числами. Просто створіть такі числа за допомогою генератора псевдовипадкових випадків, збережіть їх у списку. Потім прочитайте int after int і порівняйте його з усіма вашими здогадами. Коли буде збіг, видаліть цю запис із списку. Провівши весь файл, швидше за все, у вас залишиться більше однієї здогадки. Використовуйте будь-який з них. У рідкісному (10% навіть у гіршому випадку) випадку, коли не здогадується, отримайте новий набір випадкових цілих чисел, можливо, цього разу більше (10-> 99%).

Споживання пам’яті: кілька десятків байтів, складність: O (n), накладні витрати: невимогливі, оскільки більшість часу буде проведено у неминучому доступі на жорсткому диску, а не в порівнянні з ints.


Найгіршим випадком, коли ми не припускаємо статичного розподілу, є те, що кожне ціле число відбувається max. один раз, оскільки тоді у файлі не зустрічається лише 1 - 4000000000 / 2³² ≈ 6% усіх цілих чисел. Тож вам знадобиться ще кілька здогадок, але це все одно не буде коштувати шкідливих пам’яті.


5
Я радий бачити, що хтось подумав про це, але чому тут внизу? Це альго з 1 проходом… 10 Мбайт вистачає на 2,5 М здогадки, а 93% ^ 2,5 М ≈ 10 ^ -79000 справді незначний шанс потребувати повторного сканування. Завдяки накладним наборам двійкового пошуку він проходить швидше, якщо використовувати менше здогадок! Це оптимально і в часі, і в просторі.
Potatoswatter

1
@Potatoswatter: добре, що ви згадали двійковий пошук. Це, мабуть, не варто накладних витрат, якщо використовувати лише 5 здогадок, але, безумовно, це 10 і більше. Ви навіть можете зробити здогади 2 М, але тоді вам слід зберегти їх у хеш-наборі, щоб отримати O (1) для пошуку.
близько

1
@Potatoswatter Еквівалентна відповідь Бен Хейлі знаходиться у верхній частині
Брайан Гордон

1
Мені подобається такий підхід, але я б запропонував покращення збереження пам’яті: якщо у вас є N біт індексованого сховища плюс деяке постійне сховище, визначте настроювану оборотну 32-бітну функцію скремблювання (перестановку), виберіть довільну перестановку та очистіть усі індексовані біти. Потім прочитайте кожне число з файлу, зашифруйте його, і якщо результат менше N, встановіть відповідний біт. Якщо якийсь біт не встановлений в кінці файлу, перевірте функцію скремблювання на його індекс. Маючи 64 КБ пам’яті, можна ефективно перевірити понад 512 000 номерів на наявність за один прохід.
supercat

2
Звичайно, у цьому алгоритмі найгірший випадок - це той, де числа створені тим самим генератором випадкових чисел, який ви використовуєте. Якщо припустити, що ви можете гарантувати, що це не так, найкраща ваша тактика - використовувати лінійний вроджений генератор випадкових чисел для генерування списку, щоб ви пройшли простір чисел псевдовипадковим способом. Це означає, що якщо ви якимось чином не вдається, ви можете продовжувати генерувати числа до тих пір, поки не покриєте весь діапазон входів (знайшли прогалину), не дублюючи зусиль.
Деві Морган

25

Якщо у діапазону [0, 2 ^ x - 1] відсутнє одне ціле число, просто просто закресліть їх усі разом. Наприклад:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(Я знаю, що це не відповідає точно на питання , але це гарна відповідь на дуже схоже запитання.)


1
Так, легко довести [ ], що працює, коли одне ціле число відсутнє, але воно часто виходить з ладу, якщо відсутнє більше одного. Наприклад, 0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7дорівнює 0. [ Написання 2 x для 2 до x'th потужності, і a ^ b для xor b, xor всіх k <2 x дорівнює нулю - k ^ ~ k = (2 ^ x) - 1 для k <2 ^ (x-1), і k ^ ~ k ^ j ^ ~ j = 0, коли j = k + 2 ** (x-2) - значить, xor всіх, крім одного числа, є значенням зниклого]
Джеймс Уолдбі - jwpat7

2
Як я згадую в коментарі до відповіді ircmaxell: Проблема не говорить "один номер відсутній", він говорить про те, щоб знайти число, не включене до 4 мільярдів номерів у файлі. Якщо припустити 32-бітні цілі числа, то у файлі може бути відсутні близько 300 мільйонів чисел. Ймовірність того, що число xor присутніх чисел збігається з пропущеним числом, становить лише близько 7%.
Джеймс Уолдбі - jwpat7

Це відповідь, про яку я думав, коли спочатку читав питання, але при більш детальному огляді я думаю, що питання є більш неоднозначним, ніж це. FYI, це питання, над яким я думав: stackoverflow.com/questions/35185/…
Лі Нішертон,

18

Вони можуть дивитись, чи чули ви про ймовірнісний фільтр Bloom, який може дуже ефективно визначити, чи значення не є частиною великого набору, (але лише з високою ймовірністю можна визначити, що воно є членом набору.)


4
Імовірно, понад 90% встановлених можливих значень, ваш фільтр Bloom, ймовірно, повинен буде перерости в бітфілд, тому багато відповідей уже використовуються. В іншому випадку ви просто закінчитеся з марною повністю заповненою бітстрингом.
Крістофер Кройціг

@Christopher Моє розуміння фільтрів Bloom полягає в тому, що ви не отримаєте заповнений бітрей, поки не досягнете 100%
Пол

... інакше ви отримаєте помилкові негативи.
Павло

@Paul заповнений бітовий масив дає помилкові позитиви, які дозволені. У цьому випадку фільтр цвітіння, швидше за все, виродився б у випадку, коли рішення, яке було б негативним, повертає помилкове додатне.
ataylor

1
@Paul: Ви можете отримати заповнений бітрейр, як тільки кількість хеш-функцій, помножене на кількість записів, буде таким же, як і довжина вашого поля. Звичайно, це був би винятковий випадок, але ймовірність зросте досить швидко.
Крістофер Крейціг

17

Виходячи з поточного формулювання в оригінальному питанні, найпростішим рішенням є:

Знайдіть максимальне значення у файлі, а потім додайте до нього 1.


5
Що робити, якщо MAXINT включений у файл?
Петро Пеллер

@Petr Peller: Бібліотека BIGINT фактично зніме обмеження на цілий розмір.
oosterwal

2
@oosterwal, якщо ця відповідь була дозволена, то вам навіть не потрібно читати файл - просто надрукуйте стільки, скільки зможете.
Накілон

1
@oosterwal, якщо ваша випадкова величезна кількість була найбільшою, яку ви могли надрукувати, і вона була у файлі, то цю задачу вирішити не вдалося.
Накілон

3
@Nakilon: +1 Ваша точка зору. Це приблизно еквівалентно фігурній загальній кількості цифр у файлі та друкуванню числа з такою кількістю цифр.
oosterwal

14

Використовуйте a BitSet. 4 мільярди цілих чисел (якщо до 2 ^ 32 цілих чисел) упаковані в BitSet з 8 за байтом, це 2 ^ 32/2 ^ 3 = 2 ^ 29 = приблизно 0,5 Гб.

Щоб додати трохи детальніше - кожен раз, коли ви читаєте номер, встановлюйте відповідний біт у BitSet. Потім зробіть перехід через BitSet, щоб знайти перший номер, якого немає. Насправді, ви могли б зробити це так само ефективно, багаторазово вибираючи випадкове число і перевіряючи його на наявність.

Насправді BitSet.nextClearBit (0) підкаже вам перший не встановлений біт.

Дивлячись на API BitSet, схоже, він підтримує лише 0..MAX_INT, тож вам можуть знадобитися 2 BitSets - один для + 'чисел та один для номерів - але вимоги до пам'яті не змінюються.


1
Або якщо ви не хочете використовувати BitSet... спробуйте масив бітів. Робить те саме;)
jcolebrand

12

Якщо немає обмеження розміру, найшвидший спосіб - взяти довжину файлу та створити довжину файлу + 1 кількість випадкових цифр (або просто "11111 ..." s). Перевага: вам навіть не потрібно читати файл, і ви можете мінімізувати використання пам'яті майже до нуля. Недолік: Ви надрукуєте мільярди цифр.

Однак, якби єдиним фактором було мінімізація використання пам'яті, і нічого іншого не важливо, це було б оптимальним рішенням. Це навіть може отримати вам нагороду за "найгірше зловживання правилами".


11

Якщо припустити, що діапазон чисел завжди буде 2 ^ n (парна потужність 2), то ексклюзив - або буде працювати (як показано іншим плакатом). Щодо того, докажемо це:

Теорія

Враховуючи будь-який діапазон цілих чисел на основі 0, у яких 2^nвідсутні елементи, в яких один елемент відсутній, ви можете знайти цей відсутній елемент, просто впорядкуючи відомі значення разом, щоб отримати пропущене число.

Доказ

Подивимося на n = 2. Для n = 2 ми можемо представити 4 унікальних цілих числа: 0, 1, 2, 3. Вони мають бітовий візерунок:

  • 0 - 00
  • 1 - 01
  • 2 - 10
  • 3 - 11

Тепер, якщо ми подивимось, кожен біт встановлюється рівно двічі. Тому, оскільки воно встановлено парне число разів, а ексклюзивне або чисел отримає 0. Якщо одне число відсутнє, то ексклюзивне або отримає число, яке при ексклюзивному руді з пропущеним числом призведе до 0. Отже, відсутнє число та отримане виключне руде число точно однакові. Якщо ми видалимо 2, отриманий xor буде 10(або 2).

Тепер давайте розглянемо n + 1. Давайте назвемо кількість разів, у який встановлено кожен біт n, xі кількість разів, коли кожен біт встановлений n+1 y. Значення yбуде дорівнює y = x * 2тому, що є xелементи з n+1бітом, встановленим на 0, і xелементи з n+1бітом, встановленим на 1. І оскільки 2xзавжди буде парним, n+1завжди буде кожен біт встановлений парним числом разів.

Тому, оскільки n=2працює і n+1працює, метод xor буде працювати для всіх значень n>=2.

Алгоритм для діапазонів на основі 0

Це досить просто. Він використовує 2 * n бітів пам'яті, тому для будь-якого діапазону <= 32 будуть працювати 32 32 бітові цілі числа (ігноруючи будь-яку пам'ять, споживану дескриптором файлів). І це робить один прохід файлу.

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

Алгоритм для довільних діапазонів

Цей алгоритм буде працювати для діапазонів будь-якого стартового числа до будь-якого закінчувального числа, якщо загальний діапазон дорівнює 2 ^ n ... Це в основному повторно базує діапазон, щоб він був мінімальним на 0. Але для цього потрібні 2 проходи через файл (перший, щоб захопити мінімум, другий для обчислення відсутнього int).

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

Довільні діапазони

Ми можемо застосувати цей модифікований метод до набору довільних діапазонів, оскільки всі діапазони будуть перетинати потужність 2 ^ n хоча б один раз. Це працює лише за відсутності одного біта. Він займає 2 проходи несортованого файлу, але кожен раз знайде єдине пропущене число:

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

В основному, повторно заснований діапазон близько 0. Потім він підраховує кількість несортованих значень для додавання, як він обчислює виключне або. Потім він додає 1 до кількості несортованих значень, щоб піклуватися про відсутнє значення (порахуйте відсутнє). Потім продовжуйте збивати n значення, збільшуючи щоразу на 1, поки n не буде потужністю 2. Результат потім повертається до початкової бази. Зроблено.

Ось алгоритм, який я протестував у PHP (використовуючи масив замість файлу, але та сама концепція):

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

Поданий у масив з будь-яким діапазоном значень (я тестував, включаючи мінуси), з одним у тому діапазоні, якого немає, він кожного разу знаходив правильне значення.

Ще один підхід

Оскільки ми можемо використовувати зовнішнє сортування, чому б не просто перевірити наявність розриву? Якщо припустити, що файл відсортований до запуску цього алгоритму:

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;

3
Проблема не говорить про те, що "один номер відсутній", він говорить про те, щоб знайти число, яке не входить до 4 мільярдів номерів у файлі. Якщо припустити 32-бітні цілі числа, то у файлі може бути відсутні близько 300 мільйонів чисел. Ймовірність того, що число xor присутніх чисел збігається з пропущеним числом, становить лише близько 7%.
Джеймс Уолдбі - jwpat7

Якщо у вас є суміжний, але відсутній один діапазон, який не базується на нулі, додайте замість xor. sum(0..n) = n*(n+1)/2. Отже missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[]). (підсумок ідеї з відповіді @ hammar.)
Пітер Кордес

9

Підступне запитання, якщо воно не було вказано неправильно. Просто прочитайте файл один раз, щоб отримати максимальне ціле число n, і поверніться n+1.

Звичайно, вам знадобиться план резервного копіювання, якщо це n+1спричинить переповнення цілого числа.


3
Ось рішення, яке працює ... за винятком випадків, коли цього немає. Корисно! :-)
DTY

Якщо це не було цитувано неправильно, питання не ставило обмеження на тип цілого чи навіть на використану мову. У багатьох сучасних мовах цілі числа обмежені лише наявною пам'яттю. Якщо найбільше ціле число у файлі> 10 Мб, велика удача, завдання неможливе для другого випадку. Моє улюблене рішення.
Юрген Стробель

9

Перевірте розмір вхідного файлу, а потім виведіть будь-яке число, яке занадто велике, щоб бути представлене файлом такого розміру. Це може здатися дешевим трюком, але це творче рішення проблеми інтерв'ю, воно акуратно уникає проблеми з пам'яттю, а технічно це O (n).

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

Слід надрукувати 10 біт-акаунтів - 1 , що завжди буде більше 2-х біт-акаунтів . Технічно число, яке вам належить обіграти, - 2 біт-акаунти - (4 * 10 9 - 1) , оскільки ви знаєте, що у файлі є (4 мільярди - 1) інших цілих чисел, і навіть при ідеальному стисненні вони займуть щонайменше по одному шматочку кожен.


Чому б не просто Console.Write( 1 << bitcount )замість петлі? Якщо у файлі є n бітів, то будь-яке (_n_ + 1) -бітове число з провідним 1 абсолютно гарантовано буде більшим.
Еммет

@Emmet - Це просто спричинило б переповнення цілого числа, якщо тільки файл не був меншим за розмір int (4 байти в C #). C ++ може дозволити вам використовувати щось більше, але C #, здається, не дозволяє нічого, крім 32-бітових ints з <<оператором. У будь-якому випадку, якщо ви не згорнете власний гігантський цілочисельний тип, це буде дуже маленький розмір файлу. Демо: rextester.com/BLETJ59067
Джастін Морган

8
  • Найпростіший підхід - знайти мінімальну кількість у файлі та повернути на 1 менше, ніж це. Для цього використовується O (1) зберігання та O (n) час для файлу з n чисел. Однак він не вийде, якщо діапазон чисел буде обмежений, що може зробити min-1 не-числом.

  • Простий і простий метод використання растрової карти вже згадувався. Цей метод використовує O (n) час і зберігання.

  • Також згадується 2-прохідний метод з відрами від 2 до 16. Він читає 2 * n цілих чисел, тому використовує O (n) час та O (1) зберігання, але він не може обробляти набори даних з більш ніж 2 ^ 16 числами. Однак це легко розширюється до (наприклад) 2 ^ 60 64-бітових цілих чисел, виконуючи 4 проходи замість 2, і легко адаптується до використання крихітної пам’яті, використовуючи лише стільки бін, скільки вміщується в пам’яті, і збільшуючи кількість проходів відповідно, в у цьому випадку час запуску більше не O (n), а натомість O (n * log n).

  • Метод XOR'ing всіх чисел разом, згаданих поки що rfrankel, а по довжині ircmaxell відповідає на запитання, поставлене в stackoverflow # 35185 , як зазначено ltn100. Він використовує O (1) зберігання та O (n) час виконання. Якщо на даний момент ми припустимо 32-бітні цілі числа, XOR має 7% ймовірність отримання чіткого числа. Обгрунтування: задано ~ 4G виразні числа XOR разом, і приблизно. 300M не у файлі, кількість встановлених бітів у кожній бітовій позиції має однаковий шанс бути непарною або парною. Таким чином, 2 ^ 32 числа мають однакову ймовірність виникнення як результат XOR, 93% яких вже є у файлі. Зауважте, що якщо у файлі числа не всі відрізняються, ймовірність успіху методу XOR зростає.


7

Чомусь, як тільки я прочитав цю проблему, я подумав про діагоналізацію. Я припускаю довільно великі цілі числа.

Прочитайте перше число. Залиште його з нульовими бітами, поки у вас 4 мільярди біт. Якщо перший біт (високого порядку) дорівнює 0, виведіть 1; інакше виведіть 0. (Вам дійсно не потрібно ліворуч: ви просто виведете 1, якщо в номері недостатньо бітів.) Зробіть те саме з другим номером, за винятком використання другого біта. Продовжуйте файл таким чином. Ви виведете одночасно 4-мільярдний номер один біт, і це число не буде таким, як у файлі. Доведення: це було те саме, що й n-е число, тоді вони погодилися б на n-му біті, але вони не будують.


+1 для творчості (і найменший найгірший результат для однопрохідного рішення поки що).
hmakholm виїхав над Монікою

Але немає 4 мільярдів бітів, щоб їх діагоналізувати, є лише 32. Ви просто отримаєте 32-бітове число, яке відрізняється від перших 32 чисел у списку.
Брайан Гордон

@Henning Це навряд чи один прохід; вам все одно доведеться конвертувати з унарного в бінарне. Редагувати: Ну, мабуть, це один пропуск над файлом. Не звертай уваги.
Брайан Гордон

@Brian, де тут щось "одинарне"? Відповідь - це побудова двійкової відповіді один раз, і він зчитує вхідний файл лише один раз, роблячи його одним проходом. (Якщо потрібен десятковий вихід, речі стають проблематичними - тоді вам, мабуть, краще побудувати одну десяткову цифру на три вхідні числа та прийняти 10% збільшення журналу вихідного числа).
hmakholm виїхав над Монікою

2
@Henning Проблема не має сенсу для довільно великих цілих чисел, тому що, як зазначають багато людей, тривіально просто знайти найбільшу кількість і додати одне, або побудувати дуже довге число з самого файлу. Це рішення діагоналізації є особливо недоречним, оскільки замість того, щоб розгалужувати цей iбіт, ви могли б просто вивести 1 біт у 4 мільярди разів і викинути зайвий 1 в кінці. Я все в порядку з тим, що в алгоритмі є довільно великі цілі числа , але я думаю, що проблема полягає у виведенні відсутнього 32-бітного цілого числа. Це просто не має сенсу в іншому випадку.
Брайан Гордон

6

Ви можете використовувати бітові прапори, щоб позначити, чи є ціле число чи ні.

Після проходження всього файлу скануйте кожен біт, щоб визначити, чи існує число чи ні.

Якщо припустимо, що кожне ціле число є 32-бітним, вони зручно розмістяться в 1 ГБ оперативної пам’яті, якщо буде здійснено маркування бітів.


0,5 Гб, якщо ви не перевизначені байт бути 4 біта ;-)
DTY

2
@dty Я думаю, що він означає "комфортно", тому що в 1Gb буде багато місця.
corsiKa

6

Викресліть пробіл та нечислові символи з файлу та додайте 1. Ваш файл тепер містить одне число, яке не вказане у вихідному файлі.

З Reddit від Carbonetc.


Любіть це! Навіть незважаючи на те, що він шукав не зовсім відповіді ...: D
Йоганн дю Тойт

6

Просто задля повноти, ось ще одне дуже просте рішення, яке, швидше за все, займе дуже багато часу, але використовує дуже мало пам’яті.

Нехай усі можливі цілі числа - це діапазон від int_minдо int_max, і bool isNotInFile(integer)функція, яка повертає істину, якщо файл не містить певного цілого чи неправдивого іншого (порівнявши це певне ціле число з кожним цілим числом у файлі)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

Питання полягало саме в алгоритмі isNotInFileфункціонування. Будь ласка, переконайтесь, що ви розумієте питання, перш ніж відповісти.
Aleks G

2
ні, питання було "яке ціле число не у файлі", а не "це ціле число x у файлі". Функція для визначення відповіді на останнє запитання могла, наприклад, просто порівняти кожне ціле число у файлі з цілим числом, про яке йде мова, і повернути істину на відповідність.
град

Я думаю, що це законна відповідь. За винятком вводу-виводу, вам потрібно лише одне ціле число та прапор bool.
Брайан Гордон

@Aleks G - Я не розумію, чому це позначено як неправильне. Ми всі згодні, що це найповільніший алгоритм з усіх :-), але він працює, і для читання файлу потрібно лише 4 байти. Оригінальне запитання не передбачає, що файл може бути прочитаний, наприклад, лише один раз.
Simon Mourier

1
@Aleks G - правильно. Я ніколи не говорив, що ви це теж говорили. Ми просто кажемо, що IsNotInFile можна тривіально реалізувати, використовуючи цикл у файлі: Відкрити; Хоча не Є {Читайте цілий ряд; Поверніть помилкове, якщо Integer = i; Else Continue;}. На це потрібно всього 4 байти пам'яті.
Саймон Мур’є

5

Для обмеження пам’яті на 10 Мб:

  1. Перетворіть число у його двійкове подання.
  2. Створіть двійкове дерево, де зліва = 0 і праворуч = 1.
  3. Вставте кожне число у дерево, використовуючи його двійкове подання.
  4. Якщо число вже вставлено, листки вже будуть створені.

Закінчивши, просто створіть шлях, який раніше не був створений, щоб створити потрібний номер.

4 мільярди число = 2 ^ 32, тобто 10 Мб може бути недостатньо.

EDIT

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

РЕДАКТ II

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


6
... і як це впишеться в 10 Мб?
hmakholm залишився над Монікою

Як щодо: обмежте глибину BTree до чогось, що вписується в 10 Мб; це означатиме, що ви отримаєте результати в наборі {хибнопозитивний | позитивний}, і ви можете повторити це і використовувати інші методи пошуку значень.
Джонатан Дікінсон

5

Я відповім на 1 Гб версію:

Інформації у запитанні недостатньо, тому спершу я висловлю кілька припущень:

Ціле число - 32 біти з діапазоном від -2,147,483,648 до 2,147,483,647.

Псевдокод:

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}

4

Поки ми робимо творчі відповіді, ось ще одна.

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


3

Елімінація бітів

Один із способів - це усунення бітів, однак це може насправді не дати результату (швидше за все, цього не буде). Псуедокод:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

Розраховує біт

Слідкуйте за кількістю бітів; і використовувати біти з найменшими сумами для створення значення. Знову ж таки це не дає гарантії отримання правильного значення.

Логіка діапазону

Слідкуйте за упорядкованими списками діапазонів (упорядкованими до початку). Діапазон визначається структурою:

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

Пройдіть кожне значення у файлі та спробуйте видалити його з поточного діапазону. У цього методу немає гарантій пам’яті, але він повинен працювати досить добре.


3

2 128 * 10 18 + 1 (що є (2 8 ) 16 * 10 18 + 1) - чи не може це бути універсальною відповіддю на сьогодні? Це відображає число, яке не може міститись у файлі 16 EB, що є максимальним розміром файлу в будь-якій поточній файловій системі.


І як би ви надрукували результат? Ви не можете помістити його у файл, а друк на екрані зайняв би кілька мільярдів років. Неможливо продовжити робочий день із сучасними комп’ютерами.
vsz

ніколи не сказано, що нам потрібно надрукувати результат де завгодно, просто «генерувати» його. тому це залежить від того, що ви маєте на увазі під генерацією. все одно, моя відповідь - лише хитрість, щоб уникнути розробки справжнього алгоритму :)
Михайло Сагалович,

3

Я думаю, що це вирішена проблема (див. Вище), але слід пам’ятати про цікавий побічний випадок, оскільки це може запитати:

Якщо є 3229,967,295 (2 ^ 32 - 1) 32-бітових цілих чисел без повторів, і тому лише одне відсутнє, є просте рішення.

Запустіть загальний обсяг з нуля, і до кожного цілого числа у файлі додайте це ціле число з 32-бітовим переповненням (ефективно, runningTotal = (runningTotal + nextInteger)% 4294967296). Після завершення додайте 4294967296/2 до загальної кількості, знову з 32-бітним переповненням. Відніміть це від 4294967296, і результат - ціле число, що відсутнє.

Проблема "лише одне ціле число, що відсутня" вирішується лише одним запуском, і лише 64 біти оперативної пам'яті присвячені даним (32 для загальної кількості запуску, 32 для читання в наступному цілому).

Висновок: Більш загальну специфікацію надзвичайно просто зіставити, якщо ми не переймаємось тим, скільки біт повинен мати цілий результат. Ми просто генеруємо досить велике ціле число, яке не може міститися у наданому нам файлі. Знову ж таки, це займає абсолютно мінімальну оперативну пам’ять. Дивіться псевдокод.

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}

@Nakilon та TheDayTurns вказали на це в коментарях до оригінального питання
Брайан Гордон

3

Як Ryan сказав це в основному, сортуйте файл, а потім перейдіть через цілі числа, і коли значення пропущене там, у вас це є :)

EDIT у нижній частині: ОП згадувало, що файл можна сортувати, так що це правильний метод.


Однією з важливих частин є те, що ви повинні робити це під час руху, тому читати потрібно лише один раз. Доступ до фізичної пам'яті відбувається повільно.
Райан Амос

@ryan external sort є в більшості випадків сортуванням злиття, тому на останньому злитті ви можете зробити перевірку :)
ratchet freak

Якщо дані є на диску, їх доведеться завантажити в пам'ять. Це відбувається автоматично файловою системою. Якщо нам доведеться знайти одне число (проблема не визначається інакше), то читання відсортованого файлу за рядком - це найефективніший метод. Він використовує мало пам'яті і не повільніше, ніж будь-що інше - файл повинен бути прочитаний.
Тоні Енніс

Як ви будете сортувати 4 мільярди цілих чисел, якщо у вас є лише 1 ГБ пам'яті? Якщо ви використовуєте віртуальну пам'ять, це займе довгий час, коли блоки пам'яті перейдуть на сторінку та вийдуть з фізичної пам'яті.
Клас Ліндбак

4
@klas злиття сорту призначене саме для цього
храповик урод

2

Якщо ви не припускаєте 32-бітове обмеження, просто поверніть випадковим чином сформоване 64-бітове число (або 128-бітове, якщо ви песиміст). Шанс зіткнення становить 1 in 2^64/(4*10^9) = 4611686018.4(приблизно 1 на 4 мільярди). Ви б мали рацію більшість часу!

(Жартує ... вид.)


Я бачу, це вже було запропоновано :) оновлення для цих людей
Пітер Гібсон

Парадокс в день народження робить таке рішення не вартим ризику, не перевіряючи файл, щоб перевірити, чи справді ваша здогадка була справжньою відповіддю. (Парадокс від дня народження в цьому випадку не застосовується, але неодноразово викликаючи цю функцію для створення нових унікальних значень, це створює ситуацію з парадоксами до дня народження.)
Пітер Кордес,

@PeterCordes Випадково генеровані 128-бітові номери саме так працюють UUID - вони навіть згадують парадокс дня народження, коли підраховують ймовірність зіткнення на сторінці UUID
Пітер Гібсон

Варіант: Знайдіть макс у наборі, додайте 1.
Філ

Я б швидко заповнив оригінальний масив (без додаткового сховища.), А потім пройдемо через масив і повідомимо про перше ціле число, яке пропустили. Зроблено. Відповів на питання.
42 рівень
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.