Вам предоставляется файл, который содержит все возможные числа в 32-битной архитектуре. 4 числа отсутствуют в этом файле. Найдите 4 пропущенных номера


22

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


Ответы:


19

Будь то собеседование или реальная работа, ваш первый приоритет должен быть рабочим решением, которое имеет смысл для вас . Это обычно означает , что вы должны предложить первое решение , которое вы можете думать , что это просто и легко для вас , чтобы объяснить.

Для меня это означает сортировку чисел и сканирование на наличие пробелов. Но я работаю над бизнес-системами и веб-приложениями. Я не возиться с битами, и я не хочу, чтобы моя команда!

Если вы проводите собеседование на низкоуровневую работу, близкую к металлической, «сортировка», вероятно, будет встречаться с пустыми взглядами. Они хотят, чтобы вам было удобно думать о битах и ​​так далее. Ваш первый ответ должен быть: «О, я бы использовал растровое изображение». (Или битовый массив, или бит установлен.)

И затем, в любом случае - даже если вы дадите «неправильное» решение, если ваш собеседник (или начальник!) Настаивает на этом , вы можете предложить некоторые улучшения или альтернативы, сосредоточив внимание на конкретной проблемной области менеджера.

  • Сильно ограниченная оперативная память? Меньше 512 МБ?
    Сортировка на месте, на диске. Вы можете использовать в основном произвольный объем ОЗУ для оптимизации и / или буферизации отсортированных блоков.
  • Ограниченный срок?
    Используйте эту оперативную память! Сортировка уже есть O(n*log(n)). (Или O (n) для целочисленной сортировки!)
  • Ремонтопригодность?
    Что может быть проще, чем сортировка ?!
  • Не демонстрирует знание битовых флагов / полей? ( BitSet/ BitMap/ BitArray)
    Ну ладно ... иди и используй, BitArrayчтобы пометить «найденные числа». А затем отсканируйте для 0s.
  • Предсказуемая сложность в реальном времени?
    Используйте растровое решение. Это один проход по файлу и еще один проход поBitArray/BitSet(чтобы найти0s). ЭтоO(n)я думаю!

Или что угодно.

Решите проблемы, которые у вас есть на самом деле. Просто сначала решите проблему, используя наивные решения, если это необходимо. Не тратьте все время на решение проблем, которые еще не существуют.


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

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

1
@ Иван «Поскольку у вас возник вопрос на собеседовании», это не то же самое, что «Есть один конкретный ответ, который ищет каждый менеджер». ... Мне, конечно, было бы все равно, какое решение вы мне дали, если вы продемонстрировали способность решать проблему, а не увлекаться решением проблем, которые я вам никогда не давал!
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) я реализовал это в Rust: https://gist.github.com/idanarye/90a925ebb2ea57de18f03f570f70ea1f . Я выбрал текстовое представление, так как on-one будет запускать его с ~ 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 пары наборов битов (2 ГБ, по-прежнему управляемы).

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

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


3
@ Идан Эйр. Это решение требует мало кода, поэтому меньше шансов на ошибки кодирования. Я симпатичный, это время O (n). Он также не предполагает / не требует многократных проходов через огромный файл, поэтому он использует меньше места, чем алгоритм, требующий многократных проходов. Пожалуйста, опишите, что вы подразумеваете под "О, дорогой".
user949300

2
Не справляется Integer.MIN_VALUEправильно. Вы можете замаскировать бит знака вместо отрицания, чтобы исправить это.
CodesInChaos

1
Этот наивный подход требует 2 ^ 32 бит = 4 Гиб = 512 МБ для наборов битов, что является скромным объемом ОЗУ, даже в 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) RAM и занимает только один проход ввода / вывода. Работает для меня.
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;
        }
}

Затем просто переберите массив, и для тех значений, которые все еще имеют значение false, их нет в файле.

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

Даже если бы у меня не было 64-битной версии, я мог бы выделить меньшие битовые массивы. Я бы предварительно обработал файл, создав набор файлов меньшего размера, каждый из которых содержал бы диапазон [0-64000] [64001-128000] и т. Д., Которые подходили бы для доступных ресурсов окружающей среды. Пройдите через большой файл и запишите каждое число в соответствующий файл набора. Затем обработайте каждый файл меньшего размера. Это займет немного больше времени из-за этапа предварительной обработки, но это позволит обойти ограничения ресурсов, если ресурсы ограничены.


Это не похоже на обработку отрицательных чисел. (Или целые числа без знака с установленным старшим битом, если это входные данные.) Память для набора битов не должна быть проблемой даже в большинстве 32-битных систем.
user949300

@ user949300 - Правильно. Я не заметил большого потребления памяти, когда массив был инициализирован со всеми ложными значениями. Для отрицательных чисел потребуется вторичный BitArray. Может быть, bArrayNegative = new BitArrary (Int32.MaxValue). Когда число было прочитано, его можно проверить на положительное или отрицательное, а затем поместить в соответствующий битовый массив. Спасибо за комментарии.
Джон Рейнор

2

Так как это вопрос интервью, я бы показал интервьюеру некоторое понимание ограничений. Тогда, что означает «все возможные числа»? Действительно ли это 0 ... 2 <(32-1), как все догадываются? Обычные 32-битные архитектуры могут работать не только с 32-битными числами. Это просто вопрос представительства, очевидно.

Это должно быть решено в 32-битной системе, или это скорее часть ограничения на числа? Например, типичная 32-разрядная система не сможет сразу загрузить файл в оперативную память. Я бы также упомянул, что 32-битная система часто не может иметь файл, содержащий все числа, из-за ограничения размера файла. Ну, если только у него нет какой-нибудь умной кодировки, такой как «Все числа, кроме этих четырех», в этом случае проблема решается тривиально.

Но если вы действительно хотите понять вопрос как «Учитывая файл со всеми числами от 0 ... 2 ^ (32-1), кроме нескольких, дайте мне пропущенные» (а это большое, если !), Тогда Есть много способов ее решить.

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

С 512 МБ ОЗУ и однопроходным файлом: отметьте каждое число (= установите бит по этому индексу), прочитанное из файла, а затем один раз пропустите ОЗУ и увидите недостающие.


1
Некоторые хорошие вопросы, но, представляет ли 32-битная система целочисленные значения, числа с плавающей запятой или разметки, она все равно может представлять только 2 ^ 32 значения в 32-битном формате. Если вопрос «о да, мы разрешаем 128-битные сверхдлинные», то «ограничение» 32-битной архитектуры в вопросе намеренно вводит в заблуждение. Тем не менее, отличный вопрос задать интервьюеру, потому что многие спецификации вводят в заблуждение или плохо написаны. Ваше реальное решение - это BitSet, как у меня.
user949300

@ user949300 Да - и невозможно узнать, что ищет интервьюер. Если последний человек, которого они наняли, был парнем, который «взломал стек, прежде чем думать», ваш ответ должен быть другим, чем если бы он «абсолютно не имел представления об архитектуре» или «играл в игру оптимизации». :) Я работал с большими битовыми наборами раньше (хотя и не на Java), поэтому они естественно приходят мне на ум. И может быть принят для более низкой памяти, если это необходимо (ведро). Наборы битов также решают «проблему сортировки» в комментариях выше с линейным временем с 512 МБ ОЗУ.
Эйко

0

Один из подходов, который легко запомнить и легко сформулировать в интервью, состоит в том, чтобы использовать тот факт, что, если вы посмотрите на все числа в N битах, каждый бит будет установлен ровно в половине этих значений, а не в другой половине. ,

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

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

Например, используя клев, у вас есть следующие значения:

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.

Предостережение: я не проверял это. Но я думаю, что это должно работать. :)

Теперь в реальной жизни я согласен с другими ответами: первые вопросы будут об окружающей среде. Есть ли у нас оперативная память (сколько), это файл на устройстве хранения с прямым доступом, одноразовая операция (оптимизация не требуется) или критическая (количество циклов), есть ли у нас внешняя утилита сортировки? и т. д.
Затем найдите компромисс, приемлемый для контекста. По крайней мере, это показывает, что вы начинаете анализировать проблему, прежде чем искать алгоритм.


-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

Редактировать. Downvoters: Это опубликованный учебник O (n) решение точной проблемы, указанной в OP.


1
Примечательно, что эта связанная книга посвящена обработке потоков. В частности, потоковая обработка в рамках ограничений. Тем не менее, я, безусловно , считаю, что это происхождение вопроса, который видел ФП, поскольку в остальном он довольно тривиален. Более того, вы на самом деле не ответили на вопрос. У вас будет +1 от меня, если вы сможете убедительно сформулировать это как «оригинальный» или «намеченный» вопрос и объяснить решение ... но это ничего не дает как есть.
svidgen

1
Этот ответ (в интервью) просто показывает, что вы читаете книгу. Ничего о ваших навыках или мыслительных процессах. А как вы "гуглите все стандартные вопросы " перед собеседованием? Есть ли какой-то конечный список «всех вопросов, когда-либо задаваемых на собеседовании», которые я пропустил?
user949300

1
@ также это подчеркивает трудность найма хорошего кандидата! Если "хорошие" просто хорошо подготовлены к вопросам собеседования ... Становится трудно нанять кого-то, кто действительно может решить мои бизнес-проблемы?
svidgen

1
@ewan Чтобы быть ясным, я высмеивала мою неправильную пунктуацию. ... В любом случае, имейте в виду, я также получил довольно много предложений о работе в мой день, даже будучи чертовски невежественным в отношении стандартных вопросов и ответов, подобных этому. И теперь, как менеджер по найму, я могу пообещать вам, что не хочу читать подробные ответы ... Хотя я понимаю, что у некоторых менеджеров будут разные потребности.
svidgen

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