Вам надається файл, який містить усі можливі числа в 32-розрядної архітектурі. У цьому файлі відсутні 4 номери. Знайдіть 4 пропущені числа


22

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


Відповіді:


19

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

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

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

І тоді, в будь-якому випадку - навіть якщо ви даєте "неправильне" рішення, якщо ваш інтерв'юер (або начальник!) Натискає на нього , ви можете запропонувати деякі вдосконалення чи альтернативи, зосередившись на певній проблемі менеджера.

  • Сильно обмежена оперативна пам'ять? Менше 512 МБ?
    Сортуйте його на місці, на диску. Ви можете використовувати в основному довільну кількість оперативної пам’яті для оптимізації та / або буферного сортування блоків.
  • Обмежений час?
    Використовуйте цю ОЗУ! Сортування вже O(n*log(n)). (Або O (n) для цілого ряду відра!)
  • Технічне обслуговування?
    Що може бути простіше, ніж сортування ?!
  • Не демонструє знання бітових прапорів / полів? ( BitSet/ BitMap/ BitArray)
    Ну гаразд ... ідіть уперед і використовуйте, BitArrayщоб позначити "знайдені числа". А потім скануйте на 0's.
  • Передбачувана складність у режимі реального часу?
    Скористайтеся растровим рішенням. Це один пропуск над файлом і інший пропуск надBitArray/BitSet(щоб знайти0імена). ОцеO(n), я думаю!

Або що завгодно.

Вирішіть проблеми, які ви насправді маєте. Просто вирішіть проблему спочатку, використовуючи наївні рішення, якщо необхідно. Не витрачайте часу на вирішення проблем, які ще не існують.


Я не настільки впевнений у доцільності сортування 4 мільярдів чисел наївним підходом, не кажучи вже про диск. Ніколи не пробував цього.
Ейко

1
@Eiko Ну ... і знову ж таки, головне - це не робити надто складних речей. Перший крок - просто вирішити проблему, будь-яким способом, який можна подумати, щоб її вирішити, навіть якщо це наївно. Я не можу навіть підкреслити рівень розчарування вашого майбутній роботодавець матиме , якщо ви проводите час ітерації , щоб переконатися , що у вас є з рішенням «права» , коли бізнес тільки потребує в рішенні. Доведіть, що ви можете зробити і те, і інше! Доведіть, що ви можете швидко вирішити проблеми, а потім визначити потенційні проблеми, які варто рефакторингу та / або оптимізувати за необхідності .
svidgen

1
@Ewan "Оскільки у вас виникло питання на співбесіді", це не те саме, що "є одна конкретна відповідь, яку шукає кожен менеджер". ... Мені, звичайно, було б байдуже, яке рішення ви мені дали, доки ви продемонстрували вміння вирішувати проблему і не зациклювалися на вирішенні проблем, які я вам ніколи не давав!
svidgen

1
Ви пропускаєте суть. Це питання та його різновиди зустрічаються в книгах загадок програмування та інтерв'ю. Це не складено особою, яка задає питання. 32-бітний матеріал, як передбачається, унеможливить, відстежуючи числа чи сортуючи. Його просто комп'ютери стали швидшими / більшими з моменту написання.
Еван

1
@Ewan: ти все ще припускаєш, що твій екземпляр запитання має ті самі обмеження, що й ОП. ОП не сказав, що його алгоритм повинен працювати на 32-бітній машині, він навіть не сказав, що він взагалі повинен працювати на комп'ютері, концептуальний алгоритм може бути відповідним. Він також не вказує, що означає "всі можливі числа", оскільки математика довільних розмірів можлива для навіть 8-бітних мікроконтролерів. Дуже багато припущень, які ви робите, ви даєте абсолютні заяви.
whatsisname

19

Оскільки це файл, я припускаю, що вам дозволяється робити кілька проходів. Спочатку створіть масив з 256 лічильників, повторіть файл і для кожного числа збільште лічильник, індексований як перший байт числа. Коли ви закінчите, більшість лічильників мають бути 2 ^ 24, але 1 до 4 лічильників повинні мати нижчі значення. Кожен з цих індексів являє собою перший байт одного з пропущених чисел (якщо їх менше 4, це тому, що кілька пропущених чисел мають один і той же перший байт).

Для кожного з цих індексів створіть інший масив з 256 лічильників та зробіть другий прохід у файлі. Цього разу, якщо перший байт є одним із значень раніше, збільшуйте лічильник у своєму масиві на основі другого байта. Коли ви закінчите, подивіться ще раз лічильники нижче 2 ^ 16, і ви отримаєте другий байт пропущених чисел, кожен з яких відповідає першому байту.

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

Часова складність - Складність O(n * log n)
простору - постійна !

Редагувати:

Власне, я вважав цей n=2^32параметр, але кількість відсутніх чисел k=4також є параметром. Якщо припустити, k<<nце означає, що складність простору є O(k).

Оновлення:

Просто для розваги (а тому, що я зараз намагаюся вивчити іржу) я реалізував це в Rust: https://gist.github.com/idanarye/90a925ebb2ea57de18f03f570f70ea1f . Я вибрав текстове представлення, оскільки він буде запускати його з ~ 2 ^ 32 числами ...


Для утримання всіх чисел у пам'яті (для декількох проходів) потрібно 4 байти * 2 ^ 32 пам'яті, що підштовхує речі. Тож більше шансів, що ви зробите всі введення / виведення чотири рази. Але іншої використаної пам'яті надзвичайно мало, тому велика робота там.
user949300

1
@ user949300 Я припускаю, що це рішення читає файл фрагмент за частиною, а не завантажує все це в пам'ять відразу
Річард Тінгл

"більшість лічильників мають бути на рівні 2 ^ 24, але у 1 до 4 лічильників повинні бути нижчі значення" - неправильно: може бути 0, при цьому всі пропущені значення ділять перший байт (можливий також другий і третій). Далі: скільки масиву ви створюєте у другому проході? 256, 1 - 4 рази 256, 256 разів 256? А далі в третій і вперед проходять?
Бернхард Хіллер

3
@BernhardHiller Файл містить усі можливі числа в 32-бітному просторі, за винятком 4 різних номерів. Таким чином, всі перші байти будуть відбуватися, лише у 1 до 4 з них буде менше звернень.
Лассе В. Карлсен

@ LasseV.Karlsen дякую, тепер я розумію алгоритм.
Бернхард Гіллер

6

Якби це Java, ви можете використовувати BitSet. Ну, два з них, тому що вони не можуть цілком утримувати всі 32 бітні числа. Скелетний код, можливо, баггі:

BitSet bitsetForPositives = new Bitset(2^31);  // obviously not 2^31 but you get the idea
BitSet bitsetForNegatives = new Bitset(2^31);

for (int value: valuesTheyPassInSomehow) {
  if ((value & 0x80000000) == 0)
     bitsetForPositives.set(value );
  else
     bitsetForNegatives.set(value & ~0x80000000);
}

Потім використовуйте BitSet.nextClearBit()для пошуку того, хто відсутній.

Примітка додана набагато пізніше:

Зауважте, що за допомогою цього алгоритму паралельно запускати частину, що забирає час, досить легко . Скажімо, початковий файл розділено на чотири приблизно рівні частини. Виділіть 4 пари BitSets (2 Гб, все ще керовані).

  1. Мають чотири потоки паралельно, кожен обробляє один файл у власну пару BitSets.
  2. Після завершення поверніться до одного потоку або бітсетів (тривіальний час), а потім зателефонуйте nextClearBit чотири рази (також досить тривіальний час).

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


3
@Idan Ayre. Це рішення вимагає мало коду, тому менше шансів помилок кодування. Я гарний, це час O (n). Він також не передбачає / вимагає декількох проходів через величезний файл, тому він використовує менше місця, ніж алгоритм, що вимагає декількох проходів. Будь ласка, уточнюйте, що ви маєте на увазі під словом «О, дорогий».
user949300

2
Не працює Integer.MIN_VALUEправильно. Ви можете замаскувати біт знака замість того, щоб заперечувати його.
CodesInChaos

1
Цей наївний підхід потребує 2 ^ 32 біт = 4 Gib = 512 MiB для біт-наборів, що є скромною кількістю оперативної пам’яті навіть у 32-бітовій системі.
CodesInChaos

Якщо мова вибору не має вбудованих бітових наборів, емулюючи їх за допомогою байтового масиву. Наприклад у C #:bool GetBit(byte[] byteArray, uint index) { var byteIndex = index >> 3; var bitInByte = index & 7; return (byteArray[byteIndex] >> bitInByte) & 1 != 0; }
CodesInChaos

1
@JoulinRouge (і JacquesB) Отже, ми погоджуємось, що це лінійно за часом, використовує скромну (1/2 Gig) оперативну пам’ять і займає лише один прохід вводу-виводу. Працює для мене.
user949300

5

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

C #

var bArray = new BitArray(Int32.MaxValue);

//Assume the file has 1 number per line
using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
            var n = int32.Parse(s);
            bArray[n] = true;
        }
}

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

Ви можете розбити файл на менші шматки, але мені вдалося виділити повний масив максимального розміру int32 (2147483647) на моєму ноутбуці потужністю 16,0 ГБ під управлінням Windows 7 (64 біт).

Навіть якби я не працював на 64 біті, я міг би виділити менші бітові масиви. Я би попередньо обробив файл, створивши набір менших файлів, кожен з діапазоном номерів [0-64000] [64001-128000] тощо, який би був відповідним для наявних екологічних ресурсів. Пройдіть великий файл і запишіть кожне число у відповідний набір файлів. Потім обробіть кожен менший файл. Через крок попередньої обробки це займе трохи більше часу, але це дозволить обійти обмеження ресурсів, якби ресурси були обмежені.


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

@ user949300 - Правильно. Я не помітив великого споживання пам'яті, коли масив був ініціалізований зі всіма помилковими значеннями. Для негативних чисел потрібен буде вторинний BitArray. Можливо bArrayNegative = новий BitArrar (Int32.MaxValue). Коли число було прочитане, його можна було перевірити на позитивне чи негативне, а потім помістити у відповідний бітовий масив. Дякуємо за коментарі.
Джон Рейнор

2

Оскільки це питання інтерв'ю, я б показав інтерв'юеру деяке розуміння обмежень. Тоді, що означає "всі можливі числа"? Це дійсно 0 ... 2 <(32-1), як усі здогадуються? Звичайні 32-бітні архітектури можуть працювати з багатьма більш ніж 32-бітовими числами. Очевидно, це лише питання представництва.

Це має бути вирішено в 32-бітній системі, чи це, скоріше, частина обмеження чисел? Наприклад, типова 32-бітна система не зможе завантажити файл відразу в ОЗУ. Я також зазначу, що 32-бітна система часто не зможе мати файл, що містить усі числа через обмеження розміру файлу. Ну, якщо тільки воно не має розумного кодування, як-от "Усі числа, крім цих чотирьох", і в цьому випадку проблема вирішується тривіально.

Але якщо ви дійсно хочете зрозуміти питання як "З огляду на файл із усіма числами від 0 ... 2 ^ (32-1), крім кількох, дайте мені відсутні" (і це великий, якщо !), То Є багато способів вирішити це.

Тривіальне, але нездійсненне. Для кожного можливого числа скануйте файл і перевірте, чи він там є.

За допомогою 512 Мб оперативної пам’яті та одного проходу через файл: позначте кожне число (= встановлений біт у цьому індексі), прочитане з файлу, а потім передайте оперативну пам’ять один раз і побачите пропущені.


1
Деякі хороші запитання, але чи представляє 32-бітна система int, floats або huzziwigs, вона все ще може представляти лише 2 ^ 32 значення в 32 бітах. Якщо питання "о так, ми допускаємо 128-бітові ультра-довгі", тоді 32-бітова архітектурна "обмеженість" у питанні свідомо вводить в оману. І все-таки чудове запитання інтерв'юеру, оскільки багато специфікацій є оманливими або погано написаними. Ваше фактичне рішення - такий, як у мене BitSet.
user949300

@ user949300 Так - і неможливо дізнатися, що шукає інтерв'юер. Якщо останнім, кого вони найняли, був хлопець, який "зламав стеки перед тим, як подумати", то ваша відповідь повинна бути іншою, ніж якщо б це був "абсолютно не має уявлення про архітектуру" або "гра в оптимізаційну гру". :) Я раніше працював з великими бітсетами (хоч і не на Java), тому вони приходять мені в голову природно. І може бути використаний для зниження пам'яті, якщо це необхідно (ковтання). Бітсети також вирішують "проблему сортування" у коментарях вище в лінійному часі з 512 Мб оперативної пам'яті.
Ейко

0

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

Якщо ви повторите всі значення у файлі і збережете 32 підрахунки значень у кінці, ви отримаєте 32 значення, які точно (2 ^ 32/2) або трохи менше цього значення. Різниця, що максимальна (2 ^ 32/2) і загальна, дає вам загальний біт, встановлений у кожному положенні пропущених значень.

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

Наприклад, використовуючи nibble, у вас є такі значення:

1010
0110
1111
0111
1101
1001
0100
0101
0001
1011
1100
1110

Загальна кількість бітів, встановлених у кожній позиції, становить:

7867

Віднімаючи ті з 8 (4 ^ 2/2), отримуємо:

1021

Що означає наступні можливі набори з 4 значень:

1000
0000
0011
0010

1010
0001
0010
0000

(пробачте мене, якщо я пропустив будь-яке, я це роблю лише з виду)

А потім знову переглядаючи оригінальні цифри, ми знаходимо 1010, тобто перший набір був відповіддю.


але вам доведеться знайти 4 числа, не одне
freedev

@freedev Ви праві. Ось що це робить. Набір з чотирьох чисел - це чотири числа ... в наборі.
JimmyJames

Цікаво, але ви замовчуєте determine all the possible sets of 4 values that could give those totals. Я дійсно думаю, що це важлива частина рішення, якого немає у вашій відповіді. Це також може вплинути на часову та просторову складність.
Аллон Гуралнек

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

0

Якщо припустити, що файл сортується за збільшенням чисел:

Переконайтесь, що воно індексує містить (2³-4) цифри.
Тепер, якщо файл був повним (або якщо 4 відсутні числа були останніми 4), читання будь-якого слова у файлі в положенні N поверне відповідне значення N.

Використовуйте дихотомічний пошук по позиціях [0..2³²-4-1) для пошуку першого не очікуваного числа X1.
Виявивши це перше пропущене число, зробіть повторно диктотомію по позиціях [X1 .. (2³²-4-1)], щоб знайти друге відсутнє, X2: На цей раз, читаючи слово у позиції N, слід повернути відповідне значення N-1 якщо не було більше відсутніх номерів (оскільки ви передали один пропущений номер).
Ітерайте аналогічно для двох залишилися чисел. На третій ітерації слово читання в положенні N повинно повертати N-2, а на четвертій - N-3.

Caveat: Я цього не перевіряв. Але я думаю, що це має спрацювати. :)

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


-2

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

На це запитання та варіанти є дуже певна «правильна» відповідь, що включає XORing всіх чисел. Це повинно показувати, що ви розумієте індекси в базах даних чи щось. Таким чином, нульові бали за будь-який "може спрацювати, але не те, що написано на папері", відповідь негайно.

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

Редагувати. Ага, здається, для 4 існує інший підхід, ніж XOR

http://books.google.com/books?id=415loiMd_c0C&lpg=PP1&dq=muthukrishnan%20data%20stream%20algorithms&hl=el&pg=PA1#v=onepage&q=muthukrishnan%20data%20stream%20algorithms&f=false

Редагувати. Далі: Це рішення опублікованого підручника O (n) точної проблеми, зазначеної в ОП.


1
Зокрема, ця пов'язана книга стосується обробки потоку. Зокрема, потокова обробка в межах обмежень. З цього приводу я, безумовно , вважаю, що саме в цьому полягає питання, яке бачила ОП, оскільки це інакше досить тривіально. Більш того, ви насправді не відповіли на питання. Ви отримаєте +1 від мене, якщо зможете переконливо поставити це як "оригінальне" або "призначене" питання і пояснити рішення ... але це нічого не відповідає як є.
svidgen

1
Ця відповідь (в інтерв'ю) просто показує, що ви читали книгу. Нічого про ваші навички чи мислені процеси. І як ви "гуглите всі стандартні запитання " перед інтерв'ю? Чи є якийсь обмежений перелік "будь-яких питань, коли-небудь заданих на інтерв'ю", які я пропустив?
user949300

1
@ewan це також підкреслює труднощі найму хорошого кандидата! Якщо "хороші" просто добре підготуються до питань інтерв'ю ... Важко найняти когось, хто насправді може вирішити мої бізнес-проблеми?
svidgen

1
@ewan Щоб було зрозуміло, я висміяв свою неправильну пунктуацію. ... У будь-якому випадку, майте на увазі, я також отримав неабияку кількість пропозицій роботи, навіть не знаючи про стандартні запитання та відповіді. І тепер, будучи менеджером з найму, я можу пообіцяти тобі, що не хочу відповіді, декламовані ... Хоча я розумію, що деякі менеджери матимуть різні потреби.
svidgen

1
@Ewan Я також повинен уточнити ще одне, якщо мій тон не був прийнятий за призначенням: ви повинні переглянути свою відповідь, щоб фактично стверджувати, що проблема в книзі, що пов'язана з книжкою, - це "задумане питання". А потім відповісти на питання! ... Ви, безсумнівно , отримали б мій +1, і багато інших, і задоволення допомагати ОП у цьому.
svidgen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.