Сгенерируйте целое число, которое не входит в число четырех миллиардов заданных единиц

Мне был задан этот вопрос на собеседовании:

Для входного файла с четырьмя миллиардами целых чисел предоставьте алгоритм для генерации целого числа, которого нет в файле. Предположим, у вас 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. Просканируйте ведра, построенные на шаге 3, найдите первое ведро, в котором нет попаданий.

Код очень похож на приведенный выше.

Вывод: мы уменьшаем объем памяти за счет увеличения прохода файлов.


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


person Community    schedule 22.08.2011    source источник
comment
Что ж, я думаю, что сортировка - лучший способ сделать это. Но я не эксперт. Сортировка и отслеживание последнего числа, помещенного в файл. Если чего-то не хватает, вы узнаете. (т.е. вы вводите 101, затем появляется 103, так что вы знаете, что диапазон 102-102 отсутствует)   -  person Ryan Amos    schedule 23.08.2011
comment
Все ли целые числа в файле уникальны? Если да, вы можете сделать это за O(n) время за 10 МБ.   -  person Paul    schedule 23.08.2011
comment
@trashgod, неправильно. Для 4294967295 уникальных целых чисел у вас останется 1 целое число. Чтобы найти его, вы должны просуммировать все целые числа и вычесть это из предварительно вычисленной суммы всех возможных целых чисел.   -  person Nakilon    schedule 23.08.2011
comment
Это вторая жемчужина из «Жемчужины программирования», и я предлагаю вам прочитать все обсуждение в книге. См. books.google.com/.   -  person Alok Singhal    schedule 23.08.2011
comment
@Nakilon: Да, я ушел в час; Благодарю. Для файла, содержащего 4294967296 уникальных 32-битных целых чисел, алгоритм будет _O_ (1): return false.   -  person trashgod    schedule 23.08.2011
comment
Исходя из суперпользователя, а не из лучших программистов (пока!) - я бы сказал, делайте это, как обычно, без ограничений памяти, и позвольте файлу подкачки подбирать слабину!   -  person Wil    schedule 23.08.2011
comment
@William Да ... это не сработает. ОС приведет к сбою вашего приложения задолго до того, как вы завершите алгоритм, и ваше приложение приведет к тому, что вся машина очень быстро увязнет. Лучше притвориться, что файла подкачки не существует.   -  person Phil    schedule 23.08.2011
comment
@nakilon Ты серьезно? У вас есть четыре миллиарда целых чисел. Даже если бы все они были меньше миллиона, какой тип хранилища вы бы использовали, чтобы получить сумму миллиона целых чисел? Действительно плохая идея.   -  person Richard    schedule 23.08.2011
comment
@Richard, в моем хранилище будет 1 целочисленная переменная.   -  person Nakilon    schedule 23.08.2011
comment
@Nakilon Суммирование чисел от 1 до 1 миллиона = 500 000 500 000. Это не 32-битное целое число. А как насчет ваших чисел от 1 до 4 миллиардов? Подумав, это могло бы сработать, но хранилище было бы вовсе не 1 целочисленной переменной.   -  person Richard    schedule 23.08.2011
comment
@Richard, 64-битное int было бы более чем достаточно большим.   -  person cftarnas    schedule 23.08.2011
comment
ответ на этот вопрос можно найти на странице 202 книги Гейл Лаакман «Взломать книгу интервью по кодированию». не уверен, смогу ли я опубликовать решение. но в любом случае, я уверен, что есть гораздо лучшие ответы ...   -  person Dhruv Gairola    schedule 23.08.2011
comment
int getMissingNumber(File inputFile) { return 4; } (ссылка)   -  person johnny    schedule 23.08.2011
comment
@ Ричард, вы когда-нибудь слышали о переполнении в цифровой арифметике?   -  person Nakilon    schedule 23.08.2011
comment
Это обсуждение перемещено сюда. @Nakilon   -  person Richard    schedule 23.08.2011
comment
@cftarnas, 32-битного int будет достаточно.   -  person Nakilon    schedule 23.08.2011
comment
@Nakilon Как вы предлагаете хранить сумму (почти) всех чисел от 0 до 2^32 - 1 в целом числе, которое может хранить только значения до 2^32 - 1? Вам понадобится 64-битное целое число (или два 32-битных целых числа) для хранения результата.   -  person Brian Gordon    schedule 23.08.2011
comment
@Nakilon Фактически, 64-битное целое число асимптотически едва ли достаточно для сохранения результата. Возведение числа в квадрат удвоит количество цифр, необходимых для его безопасного представления (с любым основанием). Это действительно постоянный коэффициент 1/2 в n(n-1)/2, который избавляет нас от необходимости слишком много думать об этом.   -  person Brian Gordon    schedule 23.08.2011
comment
Не имеет значения, что вы не можете хранить сумму всех целых чисел от 1 до 2 ^ 32, потому что целочисленный тип в таких языках, как C / C ++, ВСЕГДА сохраняет такие свойства, как ассоциативность и коммуникативность. Это означает, что, хотя сумма не будет правильным ответом, если вы вычислите ожидаемую с переполнением, фактическую сумму с переполнением, а затем вычтите результат, результат все равно будет правильным (при условии, что он сам не переполняется).   -  person thedayturns    schedule 24.08.2011
comment
@thedayturns, ха-ха, они были быстрее тебя и их больше, чем нас. Они уже взбесились и зашли в мой профиль, чтобы вычеркнуть мои вопросы и ответы только потому, что чувствовали, что чего-то не понимают, например, модульной арифметики. Это забавно. Они сделали мой день)   -  person Nakilon    schedule 24.08.2011
comment
@Nakilon - ваш ответ был моей первой мыслью, и модульная арифметика определенно работает в случае целых чисел 2 ^ 32-1, но я полагаю, что это не сработает, если будет 4 миллиарда целых чисел. Т.е. на самом деле отсутствует 294967296 целых чисел из полного набора.   -  person James    schedule 24.08.2011
comment
@ Джеймс, конечно же! наша ветка в комментариях была о 4294967295 уникальных целых числах для trashgod. К сожалению, он удалил свой первый комментарий, начинающийся с обсуждения.   -  person Nakilon    schedule 24.08.2011
comment
Размер файла 4 × 109 × 4 байта = 16 ГБ. Я не согласен. Файл может содержать текстовую информацию (числа не как двоичные данные, а как их текстовое представление). Также ничего не сказано о том, можем ли мы изменить входной файл или нет.   -  person George Gaál    schedule 24.08.2011
comment
@thedayturns ОК, да, это хороший момент. Но это ограничивает вас случаем, когда отсутствует одно целое число; метод с 64-битной суммой может найти одно пропущенное целое число из многих. Если вы хотите сделать это с 32-битной суммой, вам нужно проверить разделы О разнице.   -  person Brian Gordon    schedule 24.08.2011
comment
@Brian, в этом конкретном случае (где у вас гарантированно будут все 32-битные целые числа, кроме одного, и без дублирования), достаточно сделать все по модулю 2 ^ 32.   -  person hmakholm left over Monica    schedule 24.08.2011
comment
@Henning Это то, в чем я признался   -  person Brian Gordon    schedule 24.08.2011
comment
@SecureFish: Случай 2 переходит от вопроса к решению. Это должно быть 1 МБ или 10 МБ?   -  person idbrii    schedule 25.08.2011
comment
Цитируемый вопрос не очень точен по определению целого числа. Если бы вы приняли его как целое число, а не более техническое определение, включая явные границы (32 бита / 64 бита и т. Д.). Вы можете просто пролистать файл один раз, найти наибольшее число и добавить его.   -  person David Hayes    schedule 25.08.2011
comment
@Nakilon, ваше решение может работать в случае, когда отсутствует только одно целое число, но я не уверен, что это предполагается в вопросе, это может быть более одного целого числа.   -  person Toby Allen    schedule 27.08.2011
comment
‹< Для каждого сегмента нам нужно 4 байта, содержащие все возможности, потому что в худшем случае все 4 миллиарда целых чисел принадлежат одному и тому же сегменту. ›› Если каждый сегмент представляет все целые числа с заданным 16-битным префиксом, то, как указано, там 65536 таких целых чисел. Как все 4 миллиарда целых чисел могут принадлежать одной и той же корзине? Не могли бы мы сэкономить место, выделив 2 ^ 16 = 65536 = 2 байта на ведро вместо 4 байтов на ведро? Также, что касается случая 2, для второго прохода через файл, я полагаю, лучше всего просто использовать 65536-битный вектор, аналогичный 1-му целочисленному: 1-битовый битовый вектор в случае 1?   -  person ChaimKut    schedule 22.04.2012
comment
Суть в том, что это вопрос собеседования. Если достойный работодатель задает вам подобный вопрос, он ожидает, что вы сначала проверите некоторые предположения. Вы должны ответить, спросив, что мы говорим о математических целых числах или о числах, ограниченных фиксированной областью? Мы говорим о 32-битных величинах? и должен ли ваш ответ попадать в ту же область.   -  person Ian    schedule 24.06.2012
comment
Модифицированный подход к ведру, который записывает минимум и максимум для каждого ведра, даст вам множество чисел для выбора. Вы также можете рассчитать среднее значение для каждого сегмента на первом проходе, а на втором проходе подсчитать числа ниже среднего и выше среднего, а также стандартное отклонение, если хотите.   -  person user1772382    schedule 19.12.2013
comment
2 ^ 32 = 4 * 109 ??? как   -  person Andy897    schedule 27.11.2014
comment
Поправьте меня, если я ошибаюсь. Решение для 2-го случая неверно. Предположим, у нас есть все единицы для первой корзины и все 65536 + 10 для второй группы, все 2 * 65536 + 10 для третьей группы, ..... все 65535 * 65536 + 10 для последней группы. Затем после первого прохода мы обнаружим, что все сегменты имеют 65536 бит, и тогда мы сделаем вывод, что ни один номер не пропущен, что неверно!   -  person derek    schedule 02.04.2015
comment
В случае 1 предполагается, что все целые числа сразу считываются в битовый вектор. Учитывая размер целых чисел в окнах, не будет ли размер файла больше 1 ГБ, который сам по себе больше доступной памяти?   -  person Stacky    schedule 12.03.2017
comment
Как работающего программиста с почти 35-летним опытом эти вопросы на собеседовании по кодированию расстроили меня. Если бы мне задали такой вопрос, я бы ответил: «Зачем вы тратите время на такой отговорочный вопрос?» Показывает ли это каким-либо образом то, что разработчики здесь делают ежедневно? Что вы надеетесь узнать? Подскажет ли это вам, приду ли я вовремя, буду ли я работать полный день, готов ли я иногда поработать ОТ, чтобы завершить работу, или если я работаю и хорошо играю с другими? Нет. Это отговорка для компьютерных фанатов. Позвоните, когда будете готовы к профессиональному собеседованию. До свидания.   -  person Bob Jarvis - Reinstate Monica    schedule 04.02.2018
comment
Не думаю, что проблема сформулирована правильно / однозначно. Даже в книге. Уникальные номера или нет ???   -  person ACV    schedule 07.04.2018
comment
Может ли кто-нибудь объяснить мне цель использования здесь системы счисления?   -  person Varun    schedule 29.08.2018


Ответы (38)


Предполагая, что «целое число» означает 32 бита: 10 МБ пространства более чем достаточно, чтобы вы могли подсчитать, сколько чисел находится во входном файле с любым заданным 16-битным префиксом для всех возможных 16-битных чисел. битовые префиксы за один проход по входному файлу. По крайней мере, одна из корзин будет обработана менее 2 16 раз. Сделайте второй проход, чтобы определить, какие из возможных чисел в этом сегменте уже используются.

Если это означает более 32 бит, но все еще ограниченного размера: сделайте, как указано выше, игнорируя все входные числа, которые оказываются за пределами 32-битного диапазона (со знаком или без знака; на ваш выбор).

Если «целое» означает математическое целое число: прочтите ввод один раз и отслеживайте длину наибольшего числа самого длинного числа, которое вы когда-либо видели. Когда вы закончите, выведите максимум плюс один случайное число, которое содержит еще одну цифру. (Одно из чисел в файле может быть bignum, для точного представления которого требуется более 10 МБ, но если входными данными является файл, то вы можете, по крайней мере, представить длину всего, что вписывается в Это).

person hmakholm left over Monica    schedule 22.08.2011
comment
Идеально. Ваш первый ответ требует всего 2 проходов через файл! - person corsiKa; 23.08.2011
comment
Я почти уверен, что целое число означает 32-битное int, в противном случае вы могли бы просто отслеживать максимальное значение и возвращать max + 1. Кроме того, анализ OP утверждает, что целые числа являются 32-битными. Но, возможно, он неправильно понял вопрос интервью. - person erickson; 23.08.2011
comment
Большой размер 10 МБ? Это довольно экстремально. - person Mark Ransom; 23.08.2011
comment
@Henning: Во второй части, что вы имеете в виду, игнорируя числа вне 32-битного диапазона? - person Christoph Wurm; 23.08.2011
comment
Хороший ответ. Поскольку формат вывода не был указан, для произвольных целых чисел в некотором случае кодирования bignum я просто собирался предложить printf("googolplex\n"), что на самом деле не является алгоритмом, но работает :) - person Jack V.; 23.08.2011
comment
@Legate, просто пропустите слишком большие числа и ничего не делайте с ними. Поскольку вы все равно не собираетесь выводить слишком большое число, нет необходимости отслеживать, какие из них вы видели. - person hmakholm left over Monica; 23.08.2011
comment
Преимущество решения 1 в том, что вы можете уменьшить объем памяти, увеличивая количество проходов. - person Yousf; 23.08.2011
comment
Я немного запутался в решении 1. Вы говорите, игнорируйте высокие 16 бит и считайте, сколько раз вы видите низкие 16 бит? для этого потребуется (2 ^ 16 (байтов для младших 16 бит) * 3 (байтов, необходимых для подсчета)), что составляет 192 КБ, так что это работает, но я просто хотел убедиться, что я не сильно запутался. - person ; 23.08.2011
comment
@acidzombie: На самом деле я говорил игнорировать младшие 16 бит и подсчитывать, сколько раз вы видите каждую возможность для старших 16 бит. Но и наоборот работает одинаково хорошо. - person hmakholm left over Monica; 23.08.2011
comment
Отлично! Это однозначно правильное решение вопроса интервьюера. Мне кажется, что вопрос был в том, чтобы заставить вас придумать решение, которое будет работать даже тогда, когда вы продолжаете опускаться ниже: как насчет 10 МБ? А как насчет 1 МБ? А как насчет статических констант и двух указателей int32, которые я вам даю? Я думаю, что ваш ответ верен даже при последнем ограничении. - person Brian Gordon; 23.08.2011
comment
Используя эту технику, мне нужно всего 2 ^ 16 бит. Это для 2 ^ 16 ведер и одного бита на ведро. Поскольку мы знаем, что отсутствует ровно одно число, в одной из корзин будет нечетное количество совпадений; у остальных будет четное число. Инициализируйте битовый массив нулями. Для каждого попадания переключите соответствующий бит. Когда вы закончите, найдите 1 и затем просканируйте это ведро (повторно используя битовый массив), чтобы найти недостающее число. - person Barry Brown; 24.08.2011
comment
@Barry: Вопрос выше не означает, что отсутствует ровно одно число. Это также не говорит о том, что числа в файле не повторяются. (Следить за фактически заданным вопросом - это, вероятно, хорошая идея на собеседовании, верно? ;-)) - person Christopher Creutzig; 24.08.2011
comment
Вероятно, мне здесь что-то не хватает, но как может работать решение номер один, если мы не знаем, что в файле нет дубликатов, а я не вижу этого в вопросе? - person Thomas Padron-McCarthy; 24.08.2011
comment
@Thomas, я не вижу причин, по которым наличие дубликатов могло бы стать проблемой для решения. Обратите внимание, что вопрос не требует от нас идентифицировать все отсутствующие числа, а просто найти хотя бы одно из них. Не могли бы вы уточнить? - person hmakholm left over Monica; 24.08.2011
comment
@Henning: Предположим, что файл содержит 65536 экземпляров каждого 32-битного числа, кратного 65536, то есть чисел с 16 нулями в качестве суффикса. Тогда каждый счетчик покажет 65536. - Но подождите. Думаю, я вижу то, что упустил. Это будет означать, что в файле 4294967296 чисел, а это ровно 4 миллиарда? - person Thomas Padron-McCarthy; 24.08.2011
comment
@ Кристофер, теперь я понимаю. Я основывал свое решение на некоторых других вопросах / комментариях в обсуждениях здесь, которые отвлекали меня от формулировки исходного вопроса. - person Barry Brown; 24.08.2011
comment
Думаю, я что-то упускаю. Может кто-нибудь объяснить мне, как вести счет? Рассмотрим сценарий, в котором каждое число в файле - n. Если мы ведем подсчет, не будет ли 4 миллиардов n, требующих от нас выделения 2 ^ 32 (накладные расходы для представления 4b, максимальное количество) * 2 ^ 16 (количество перестановок Hi-бит) == всего 2 ^ 48 бит = = 32768 ГБ? что мне не хватает? - person mrBorna; 26.08.2011
comment
@mrBorna, даже счетчик, который может достигать значения в 4 миллиарда, занимает всего 32 бита, а не бит для каждого значения, которого он может достичь. И вам даже не нужно точно отслеживать - как только счетчик ведра достигает ceil (4.0e9 / 65536) = 61036, вы можете прекратить его увеличивать, потому что тогда он не может быть счетчиком с наименьшим значением, когда первый проход закончен. . Таким образом, вам действительно нужен только 16-битный счетчик для каждой корзины. - person hmakholm left over Monica; 26.08.2011
comment
@Henning Хорошо ... Итак, исходя из вашего пояснения (и спасибо вам за это), каждое ведро (из 2 ^ 16) имеет счетчик 2 ^ 16 бит. Это всего 512 МБ памяти на один запуск (хотя и меньше, если мы разделим его на несколько запусков). Значит, это не решение с двумя запусками для 10 МБ памяти, верно? - person mrBorna; 26.08.2011
comment
@mrBorna, ошибаюсь! В каждом сегменте есть двухбайтовый счетчик (шестнадцать бит, способный считать до 65535), в результате чего вся таблица занимает 128 килобайт. - person hmakholm left over Monica; 26.08.2011
comment
Гах, я все испортил, это имеет смысл. Я не знаю, почему я продолжал говорить 2 ^ 16, когда имел в виду 16 бит для счетчика, лол. Большое спасибо @Henning! - person mrBorna; 27.08.2011
comment
@HenningMakholm, а почему вся таблица 128 кб? Я думаю, что есть 2 ^ 16 ведер, которые займут 2 ^ 16/8 = 8192b, и есть счетчик 2-byte для каждого ведра, поэтому берется 2 * 2 ^ 16 = 131072b. Тогда думаю возьмут 8192 + 131072 = 139,3кб, да? - person Alcott; 01.10.2011
comment
@HenningMakholm, во-вторых, если есть дубликаты, скажем, есть 65535 дубликатов для ведра, нет других номеров для этого ведра, будет ли решение обнаруживать недостающие числа? - person Alcott; 01.10.2011
comment
@Alcott: (1) мне непонятно, для чего вам нужны отдельные биты 2 ^ 16 (8192 байта), о которых вы говорите. Сами счетчиков должно хватить. (2) Алгоритм не предназначен для поиска всех пропущенных чисел. Все, что ему нужно сделать, это найти один среди 32-битных чисел, которых нет в файле. Корзина, в которой встречается 65535 дубликатов одного числа, будет просто проигнорирована в пользу корзины с меньшим количеством совпадений. - person hmakholm left over Monica; 01.10.2011
comment
Я не понимаю, как второй проход может гарантировать получение нового числа. Каждый слот отслеживает количество целых чисел в файле с 16-битным префиксом, я вижу во втором проходе, если счетчик в слоте равен 0 или 1, когда мы сопоставляем его, мы можем найти новое число, просто добавив 1 к префиксу или прибавив 1 к текущему целому числу в файле под указателем сканирования. Однако, когда счетчик больше 1, как он дает новое число за 2-секундный проход, просто глядя на 16-битный префиксный счетчик? Кто-нибудь проливает свет, пожалуйста. - person Cong Hui; 16.09.2013
comment
@CongHui: во втором проходе мы рассматриваем только те числа с определенным 16-битным префиксом, который мы определили на первом проходе, который, как мы уже знаем, должно быть меньше 2 ^ 16 таких чисел. Затем мы записываем количество каждого 16-битного суффикса для чисел с указанным 16-битным префиксом. Должен быть 16-битный суффикс, которого нет в файле. Таким образом, число, образованное 16-битным префиксом и 16-битным суффиксом, отсутствует в файле, мы можем вывести его. - person justhalf; 18.09.2013
comment
@justhalf Попался, это просто великолепно! Спасибо - person Cong Hui; 19.09.2013
comment
@Harshdeep, под проходом мы подразумевали процесс чтения чисел из входного файла, а не из памяти. Конечно, после второго прохода (то есть второго чтения ввода) нам нужно перебрать память, чтобы найти, какой 16-битный суффикс не был установлен. - person justhalf; 21.04.2014
comment
Замечательный ответ! И он очень хорошо масштабируется для больших чисел фиксированного размера, таких как 4 прохода для 64-битных целых чисел и т. Д. - person Michael; 16.12.2014
comment
Или длину самого длинного числа! Или длина этого! Или длина этого! Или количество раз, когда вы взяли длину самого длинного числа! (Или длину самого длинного числа в виде стрелки Кнута, направленной вверх! Или ...) - person user253751; 05.02.2015
comment
Решение для 2-го случая неверно. Просто представьте следующий случай: 1-е ведро: все единицы. - person derek; 02.04.2015
comment
@deryk: Является ли 1st bucket: all 1s описанием контрпримера? Я не понимаю, какой это был бы контрпример; не могли бы вы попытаться расширить свое описание до одного или нескольких полных предложений? - person hmakholm left over Monica; 02.04.2015
comment
@ Хеннинг Махольм. На этом веб-сайте должно быть что-то не так. Я много писал, а осталось мало. В любом случае, моя точка зрения такова: если в каждом ведре есть только дубликаты одного числа, первый проход скажет вам, что ни один номер не пропущен, что неверно! Например, в первом сегменте 65536 из 10, во втором сегменте 65536 из 65536 + 10, в третьем сегменте 65536 2 * 65536 + 10, ..., в последнем сегменте 65536 из 65535 * 65536 + 10. Первый проход сообщит вам, что каждая корзина насчитывает 65536 раз, и поэтому не будет ни одного пропущенного числа. Пожалуйста, поправьте меня, если этот контрпример неверен. - person derek; 02.04.2015
comment
@deryk: 65536 корзин с 65536 копиями в каждой потребует, чтобы входной файл содержал 65536 * 65536 = 4294967296 записей - и в исходном вопросе интервью прямо сказано, что там всего 4 миллиарда чисел, что меньше этого. (Разумные умы расходятся во мнениях относительно того, должно ли, например, гигабайт означать 1024 ^ 3 или 10 ^ 9 байтов, но миллиард однозначно равен 10 ^ 9 на английском языке). - person hmakholm left over Monica; 02.04.2015
comment
@Henning Makholm, что вы имеете в виду 65536 ведер по 65536 копий в каждом? Означает ли это, что в каждом сегменте 65536 бит? или, другими словами, вы выделяете двумерный массив сегментов [65536] [65536/8]? в таком случае общий размер намного превышает 10 МБ. - person derek; 02.04.2015
comment
@deryk: Нет, я просто говорю, что ваш заявленный контрпример, похоже, описывает входной файл с 65536 * 65536 целыми числами в нем. Это не соответствует описанию в вопросе, который касается файла, содержащего всего 4 миллиарда целых чисел. (В решении я описываю, что каждый из 65536 сегментов представлен одним 16-битным счетчиком). - person hmakholm left over Monica; 02.04.2015
comment
@Henning Makholm, Или вы говорите, что читаете в файле 65536 раз и каждый раз считаете только числа в [k * 65536, k * 65536 + 65535]. Я правильно понимаю? - person derek; 02.04.2015
comment
@ Хеннинг Махольм, немного запутался. Есть ли различия между 65536 * 65536 целыми числами в файле и 4 миллиардами целых чисел в файле? 65536 * 65536 - это около 4 миллиардов. - person derek; 02.04.2015
comment
@deryk: (1) Я прочитал файл один раз и подсчитал, сколько чисел находится в каждом интервале длины 65536. Затем я выбираю интервал, по которому было выполнено наименьшее количество совпадений, и читаю файл еще раз, отслеживая теперь, какие из этих конкретных 65536 чисел были обнаружены. (2) Да, разница 65536*65536-4000000000 == 294967296. В вашем файле примерно на 295 миллионов номеров больше, чем предполагалось на 4 миллиарда. - person hmakholm left over Monica; 02.04.2015
comment
@Henning Makholm: Хорошо, мой вопрос вернулся. Для каждого 16-битного счетчика, если существуют дубликаты, подсчитываете ли вы их один или несколько раз? Например, для 1-го счетчика вы видите 1,1,1, вы считаете один или три раза? - person derek; 03.04.2015
comment
@deryk: вы подсчитываете количество строк в файле, которые содержат число в интервале, покрытом ведром. Неважно, одинаковы эти числа или разные (поскольку вы не можете позволять чему-либо зависеть от наличия дубликатов без использования слишком большого количества памяти). - person hmakholm left over Monica; 03.04.2015
comment
@Henning Makholm: теперь я полностью понимаю ваше решение. Поскольку общее количество целых чисел в файле составляет 4 миллиарда, что меньше 2 ^ 32, для 65536 сегментов всегда будет по крайней мере один сегмент, который будет выполнен менее 65536 раз. Хорошее решение! Я копнул еще немного и обнаружил, что это называется принципом ящика. - person derek; 03.04.2015
comment
для неограниченного: один проход, убрать разделители (все, что не является цифрой). - person ctrl-alt-delor; 18.12.2018
comment
Если целые числа не ограничены, не всегда возможно хранить конечное целое число, большее их всех (или, что эквивалентно, их длины), в конечном объеме пространства. - person Solomon Ucko; 16.06.2019
comment
Не могли бы вы уточнить пример с меньшим размером, например 4-битными целыми числами? не могу четко понять, мои расчеты ошибочны. @corsiKa - person vine'th; 03.09.2019

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

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

Если целые числа ограничены 32 битами, то можно сгенерировать число, которое, вероятно, будет уникальным, за один проход, используя гораздо меньше 10 МБ. Вероятность того, что псевдослучайное 32-битное целое число столкнется с одним из 4 миллиардов существующих целых чисел, составляет около 93% (4e9 / 2 ^ 32). Вероятность того, что 1000 псевдослучайных целых чисел столкнутся, меньше одного из 12 000 миллиардов миллиардов миллиардов (вероятность одного столкновения ^ 1000). Таким образом, если программа поддерживает структуру данных, содержащую 1000 псевдослучайных кандидатов, и выполняет итерацию по известным целым числам, исключая совпадения из кандидатов, почти наверняка найдется хотя бы одно целое число, которого нет в файле.

person Ben Haley    schedule 23.08.2011
comment
Я почти уверен, что целые числа ограничены. Если бы это было не так, то даже начинающий программист подумал бы об алгоритме, сделав один проход через данные, чтобы найти максимальное число, и прибавить к нему 1. - person Adrian Petrescu; 23.08.2011
comment
Буквальное угадывание случайного результата, вероятно, не принесет вам много очков на собеседовании. - person Brian Gordon; 23.08.2011
comment
@ Адриан, это было моим решением;) требует одного прохода через файл и соответствует требованиям. - person jcolebrand; 23.08.2011
comment
@ Адриан, ваше решение кажется очевидным (и это было для меня, я использовал его в своем собственном ответе), но это не очевидно для всех. Это хороший тест, чтобы увидеть, можете ли вы найти очевидные решения или собираетесь чрезмерно усложнять все, к чему вы прикасаетесь. - person Mark Ransom; 23.08.2011
comment
По мере увеличения числа int в файле возможность псевдослучайного подхода снижается. Если файл содержит, скажем, (2 ^ 32-5) целых чисел, попытка случайным образом найти одну из горстки оставшихся целых чисел нецелесообразна. - person Frank Farmer; 24.08.2011
comment
@Brian: Я думаю, что это решение одновременно и изобретательно, и практично. Я, например, дал бы много похвалы за этот ответ. - person Richard H; 24.08.2011
comment
Отличный ответ, но ... Рев. Я наихудший монстр - надоедливый, но не очень важный. Худший случай: O (N ^ 2), при условии, что вы проверяете двойники в своей случайной вещи. Тем не менее, приплывшие лодки. - person Daniel; 24.08.2011
comment
@brian, если даже шанс один из 64 миллиардов миллиардов миллиардов для вас слишком высок, просто угадайте свое число, а затем сделайте один проход, чтобы убедиться, что его не существует. - person Kevin; 24.08.2011
comment
ах здесь проходит грань между инженерами и учеными. Отличный ответ, Бен! - person TrojanName; 25.08.2011
comment
Я не согласен с этим ответом, а также с комментариями о его практичности. Я неоднократно видел катастрофические последствия якобы статистически невероятных столкновений. Одна из причин заключается в том, что вероятность столкновения в пределах K чисел, случайно выбранных из N возможных, с K ‹* N, примерно пропорциональна K ^ 2 / N, НЕ K / N, см. Парадокс дня рождения. Вторая причина - в недостатках псевдослучайности. Однопроходная проверка определенно помогает, но когда K близко к sqrt (N), вам потребуются многочисленные предположения и проходы. - person Michael; 16.12.2014
comment
@Michael, вы правы, что у псевдослучайности могут быть недостатки. В этом случае парадокс дня рождения не применим. Но ваш более общий тезис о том, что дополнительные предположения приводят к дополнительным режимам отказа, хорошо принят. Мой ответ имеет значение для тех ситуаций, когда повышение скорости стоит дополнительных усилий по внедрению и отладке. - person Ben Haley; 16.12.2014

Подробное обсуждение этой проблемы обсуждалось в Джоне Бентли «Колонка 1. Взлом устрицы» Жемчужины программирования Аддисон-Уэсли стр. 3–10

Bentley обсуждает несколько подходов, включая внешнюю сортировку, сортировку слиянием с использованием нескольких внешних файлов и т. Д., Но лучший метод, предлагаемый Bentley, - это однопроходный алгоритм с использованием битовые поля, которые он с юмором называет "чудесной сортировкой" :) Подходя к проблеме, 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.

person vine'th    schedule 23.08.2011
comment
Я не знаю книгу, но нет причин называть ее Чудесной сортировкой, так как это всего лишь бакетная сортировка с 1-битным счетчиком. - person flolo; 23.08.2011
comment
В книге Джон Бентли просто шутит ... и называет это Wonder Sort. - person vine'th; 23.08.2011
comment
Хотя этот код более переносимый, он будет уничтожен кодом написано с использованием аппаратно поддерживаемых векторных инструкций. Я думаю, что в некоторых случаях gcc может автоматически преобразовывать код для использования векторных операций. - person Brian Gordon; 23.08.2011
comment
@Brian Я думаю, что это больше похоже на супер-наивный код, который часто наиболее поддается отладке, но никогда не бывает наиболее оптимизированным. - person jcolebrand; 23.08.2011
comment
@brian Я не думаю, что Джон Бентли допускал такие вещи в свою книгу по алгоритмам. - person David Heffernan; 23.08.2011
comment
@ Брайан: Будет ли? Я думаю, это полностью зависит от того, сколько раз вы планируете запускать программу и сколько стоит ваше программирование (и отладка!). Если вам нужно запустить его только один раз, лучше написать что-нибудь простое, в котором с меньшей вероятностью будут ошибки. - person Brooks Moses; 24.08.2011
comment
@Brian: книга была написана в 1986 году и состоит из колонок, ранее написанных для журнала CACM. Я не думаю, что в то время у них были векторные инструкции с аппаратной поддержкой. - person Dave Kirby; 24.08.2011
comment
@ Дэйв: в суперкомпьютерах с 1970-х годов были векторные инструкции. Википедия (en.wikipedia.org/wiki/Vector_processor) утверждает, что первая работа была выполнена в начало 1960-х. - person Andrew Dalke; 24.08.2011
comment
@ wine'th, я не совсем понимаю, что вы имели в виду, если доступна только одна байтная память? как сделать работу с одним байтом? Можете быть более конкретными? - person Alcott; 01.10.2011
comment
@ wine'th, под 4 passes, вы имели в виду read the file 4 times, чтобы закончить работу? - person Alcott; 01.10.2011
comment
@Alcott: Пример с одним байтом был просто очень простым примером. Да под 4 проходами я имел в виду 4 раза прочитать файл. Чтобы быть более реалистичным, если у нас есть память на 1 миллиард битов, нам нужно 4 прохода, чтобы найти недостающее число среди 4 миллиардов чисел в приведенном выше примере. - person vine'th; 02.10.2011
comment
@BrianGordon, время, проведенное в оперативной памяти, будет незначительным по сравнению со временем, потраченным на чтение файла. Забудьте об оптимизации. - person Ian; 24.06.2012
comment
@BrianGordon: Что ты собираешься делать с векторами? Помните, что ваш ввод не отсортирован, поэтому для каждого элемента ввода вам нужно установить в памяти отдельный бит, и эти биты не будут смежными. x86 AVX2 собрал множество (из 32-битных элементов). У него нет разрозненных хранилищ целых чисел, не говоря уже о байтах, не говоря уже о битах в битовом поле! - person Peter Cordes; 15.08.2015
comment
@BrianGordon: Или вы говорили о петле в конце, чтобы найти первый неустановленный бит? Да, векторы ускорят это, но цикл по битовому полю с 64-битными целыми числами в поисках того, что != -1, по-прежнему будет насыщать пропускную способность памяти, работающей на одном ядре (это SIMD-в-регистре, SWAR, с битами в качестве элементов) . (Для последних разработок Intel / AMD). Вам нужно только выяснить, какой бит не установлен, после того, как вы найдете 64-битное местоположение, содержащее его. (И для этого вы можете not / lzcnt.) Справедливо отметить, что цикл однобитового теста может быть плохо оптимизирован. - person Peter Cordes; 15.08.2015
comment
Эта книга жемчужин программирования - золото. +1 за ссылку на него. Почему я не знал об этом раньше ...! - person rents; 03.09.2015

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

person Andris    schedule 23.08.2011
comment
Если наибольшее число в файле не равно max int, вы просто переполнитесь - person KBusc; 11.03.2014
comment
Каким будет размер этого файла в реальной программе, которой может потребоваться сгенерировать новое целое число и 100 раз добавить его к используемому файлу целых чисел? - person Michael; 16.12.2014
comment
Я думал об этом. Предполагая, что int - это 32 бит, просто выведите 2^64-1. Выполнено. - person imallett; 12.06.2015
comment
Если это одно целое число в строке: tr -d '\n' < nums.txt > new_num.txt: D - person Shon; 06.02.2018

Для варианта ОЗУ 1 ГБ вы можете использовать битовый вектор. Вам нужно выделить 4 миллиарда бит == массив байтов 500 МБ. Для каждого числа, которое вы читаете из ввода, установите соответствующий бит в «1». Как только вы закончите, переберите биты, найдите первый, который все еще равен «0». Его индекс - это ответ.

person Itay Maman    schedule 22.08.2011
comment
Диапазон чисел во входных данных не указан. Как работает этот алгоритм, если входные данные состоят из всех четных чисел от 8 до 16 миллиардов? - person Mark Ransom; 23.08.2011
comment
@Mark, просто игнорируйте входные данные, выходящие за пределы диапазона 0..2 ^ 32. Вы все равно не собираетесь выводить какие-либо из них, поэтому нет необходимости помнить, каких из них следует избегать. - person hmakholm left over Monica; 23.08.2011
comment
@ Отметьте любой алгоритм, который вы используете для определения того, как 32-битная строка отображается на действительное число, зависит от вас. Процесс все тот же. Единственная разница в том, как вы печатаете это как действительное число на экране. - person corsiKa; 23.08.2011
comment
Вместо повторения самостоятельно вы можете использовать bitSet.nextClearBit(0): download.oracle.com/javase/6/docs/api/java/util/ - person starblue; 23.08.2011
comment
Было бы полезно упомянуть, что, независимо от диапазона целых чисел, по крайней мере один бит гарантированно будет равен 0 в конце прохода. Это связано с принципом «ящика». - person Rafał Dowgird; 23.08.2011
comment
Если у вас нет памяти с той же скоростью, что и у вашего процессора, решение Хеннинга Махольма лучше независимо от количества оперативной памяти. - person user606723; 23.08.2011
comment
@Brian - если у вас есть 2 ^ 32 бита и 4 миллиарда целых чисел, вы можете сопоставить один бит с каждым из целых чисел, и у вас гарантированно останется слот, поскольку у вас заканчиваются целые числа, прежде чем у вас закончатся бит, вам придется сопоставить два бита с одним целым числом, что является противоречием. Итак, если предположить, что 4B действительно означает 2 ^ 32, как я подозреваю, Раф сделал, он прав. - person James; 24.08.2011

Если это 32-битные целые числа (вероятно, из-за выбора ~ 4 миллиардов чисел, близких к 2 32), ваш список из 4 миллиардов чисел займет не более 93% возможных целых чисел (4 * 10 9 / (2 32)). Итак, если вы создаете битовый массив из 2 32 битов с каждым битом, инициализированным нулем (что займет 2 29 байтов ~ 500 МБ ОЗУ; помните, что byte = 2 3 бит = 8 бит), прочитайте ваш список целых чисел и для каждого int установите соответствующий элемент битового массива от 0 до 1; а затем прочитайте свой битовый массив и верните первый бит, который все еще равен 0.

В случае, если у вас меньше оперативной памяти (~ 10 МБ), это решение необходимо немного изменить. 10 МБ ~ 83886080 бит по-прежнему достаточно для создания битового массива для всех чисел от 0 до 83886079. Итак, вы можете прочитать свой список целых чисел; и записывать только #, которые находятся в диапазоне от 0 до 83886079 в вашем битовом массиве. Если числа распределены случайным образом; с подавляющей вероятностью (отличается на 100% примерно на 10 -2592069) вы обнаружите отсутствующее int). Фактически, если вы выберете только числа от 1 до 2048 (всего с 256 байтами ОЗУ), вы все равно найдете недостающее число в подавляющем проценте (99.9999999999999999999999999999999999999999999999999999999995%) времени.

Но, скажем, вместо того, чтобы иметь около 4 миллиардов номеров; у вас было что-то вроде 2 32 - 1 числа и менее 10 МБ ОЗУ; поэтому любой небольшой диапазон целых чисел имеет небольшую вероятность не содержать число.

Если вам гарантировано, что каждый int в списке уникален, вы можете суммировать числа и вычесть сумму с пропущенным одним # до полной суммы (½) (2 32) (2 32 - 1) = 9223372034707292160, чтобы найти отсутствующее int. Однако, если 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 КБ. Таким образом, bin 0 равен (от 0 до 65535 = 2 16 -1), bin 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 миллиардов целых чисел; и подсчитайте, сколько int попадает в каждую из 2 16 корзин, и найдите incomplete_bin, в котором нет всех 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
person dr jimbob    schedule 23.08.2011
comment
Такой потрясающий ответ. Это действительно сработает; и имеет гарантированный результат. - person Jonathan Dickinson; 23.08.2011
comment
@dr jimbob, что, если в корзине только одно число, и у этого единственного числа есть 65535 дубликатов? Если это так, то в корзине все равно будет 65536, но все 65536 чисел будут одинаковыми. - person Alcott; 08.10.2011
comment
@Alcott - Я предполагал, что у вас есть 2 ^ 32-1 (или меньше) чисел, поэтому по принципу ячейки вы гарантированно получите один лоток с менее чем 65536 счетчиками для более подробной проверки. Мы пытаемся найти только одно отсутствующее целое число, а не все из них. Если у вас было 2 ^ 32 или более чисел, вы не можете гарантировать отсутствие целого числа и не сможете использовать этот метод (или иметь гарантию с самого начала, что целое число отсутствует). В таком случае лучшим выбором будет грубая сила (например, прочитать массив 32 раза; проверить первые 65536 # с в первый раз; и остановиться, как только будет найден ответ). - person dr jimbob; 08.10.2011
comment
Умный метод верхнего 16 / нижнего 16 был опубликован ранее Хеннингом: stackoverflow.com/a/7153822/224132. Однако мне понравилась идея их сложения для уникального набора целых чисел, в котором отсутствует ровно один член. - person Peter Cordes; 15.08.2015
comment
@PeterCordes - Да, решение Хеннинга предшествует моему, но я думаю, что мой ответ все еще полезен (прорабатывая несколько вещей более подробно). Тем не менее, Джон Бентли в своей книге Programming Pearls предложил вариант с несколькими проходами для этой проблемы (см. Ответ винодела) задолго до того, как существовал stackoverflow (не то чтобы я утверждал, что кто-то из нас сознательно украл оттуда, или что Бентли был первым, кто проанализировать эту проблему - вполне естественное решение для разработки). Два прохода кажутся наиболее естественными, когда ограничение состоит в том, что вам больше не хватает памяти для однопроходного решения с гигантским битовым массивом. - person dr jimbob; 15.08.2015
comment
Да, я согласен, это хороший ответ для анализа / объяснения. Сравнивая с другими ответами, я вижу, что идея добавления их похожа на ответы XOR-them-all. - person Peter Cordes; 15.08.2015

Зачем все усложнять? Вы просите целое число, которого нет в файле?

Согласно указанным правилам, единственное, что вам нужно сохранить, - это наибольшее целое число, которое вы встретили в файле до сих пор. Как только весь файл будет прочитан, верните число на 1 больше, чем это.

Нет никакого риска попасть в maxint или что-то еще, потому что, согласно правилам, нет ограничений на размер целого числа или числа, возвращаемого алгоритмом.

person Pete    schedule 23.08.2011
comment
Это сработало бы, если бы в файле не было max int, что вполне возможно ... - person PearsonArtPhoto; 23.08.2011
comment
В правилах не указано, что это 32-битный или 64-битный или что-то еще, поэтому в соответствии с указанными правилами нет max int. Целое число - это не компьютерный термин, это математический термин, определяющий положительные или отрицательные целые числа. - person Pete; 23.08.2011
comment
Достаточно верно, но нельзя предполагать, что это 64-битное число или что кто-то не будет просто тайком ввести максимальное число int только для того, чтобы запутать такие алгоритмы. - person PearsonArtPhoto; 23.08.2011
comment
Полное понятие max int недействительно в контексте, если не указан язык программирования. например посмотрите определение длинного целого числа в Python. Это безгранично. Крыши нет. Вы всегда можете добавить один. Вы предполагаете, что он реализуется на языке, который имеет максимально допустимое значение для целого числа. - person Pete; 23.08.2011

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

  1. Начните с допустимого диапазона чисел от 0 до 4294967295.

  2. Рассчитайте среднюю точку.

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

  4. Если нет равных чисел, все готово. Число средней точки - это ответ.

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

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

По сути, это то же самое, что и решение Хеннинга, за исключением того, что он использует два бина вместо 16k.

person hammar    schedule 23.08.2011
comment
Это то, с чего я начал, прежде чем начал оптимизацию по заданным параметрам. - person hmakholm left over Monica; 24.08.2011
comment
@ Хеннинг: Круто. Это хороший пример алгоритма, в котором легко настроить компромисс между пространством и временем. - person hammar; 24.08.2011
comment
@hammar, а что, если эти числа встречаются более одного раза? - person Alcott; 02.10.2011
comment
@Alcott: тогда алгоритм выберет более плотную корзину вместо более разреженной, но по принципу ячейки он никогда не сможет выбрать полностью полную корзину. (Меньшее из двух значений всегда будет меньше диапазона ячейки.) - person Peter Cordes; 15.08.2015

РЕДАКТИРОВАТЬ Хорошо, это было не совсем продумано, поскольку предполагается, что целые числа в файле соответствуют некоторому статическому распределению. По-видимому, в этом нет необходимости, но даже тогда стоит попробовать следующее:


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

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

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

Потребление памяти: несколько десятков байтов, сложность: O (n), накладные расходы: можно изменить, поскольку большая часть времени будет тратиться на неизбежные обращения к жесткому диску, а не на сравнение целых чисел в любом случае.


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

person leftaroundabout    schedule 23.08.2011
comment
Когда ограничением является память, а не время, это, вероятно, лучший метод. Продолжайте перечитывать файл снова и снова - person jcolebrand; 23.08.2011
comment
Я рад видеть, что кто-то еще думает об этом, но почему это здесь, внизу? Это однопроходный алгоритм ... 10 МБ достаточно для 2,5 млн предположений, а 93% ^ 2,5 млн ≈ 10 ^ -79000 - действительно ничтожно малый шанс, что потребуется второе сканирование. Из-за накладных расходов на двоичный поиск он выполняется быстрее, если вы используете меньше догадок! Это оптимально как по времени, так и по пространству. - person Potatoswatter; 24.08.2011
comment
@Potatoswatter: хорошо, что вы упомянули двоичный поиск. Это, вероятно, не стоит накладных расходов при использовании только 5 предположений, но, безусловно, при 10 или более. Вы даже можете сделать 2 M предположений, но тогда вы должны сохранить их в хеш-наборе, чтобы получить O (1) для поиска. - person leftaroundabout; 24.08.2011
comment
@Potatoswatter, эквивалентный ответ Бена Хейли вверху - person Brian Gordon; 24.08.2011
comment
Мне нравится этот подход, но я бы предложил улучшение экономии памяти: если доступно N бит индексированного хранилища плюс некоторое постоянное хранилище, определите настраиваемую обратимую 32-битную функцию скремблирования (перестановку), выберите произвольную перестановку и очистите все индексированные биты. Затем прочтите каждое число из файла, скремблируйте его и, если результат меньше N, установите соответствующий бит. Если какой-либо бит не установлен в конце файла, отмените функцию скремблирования его индекса. Имея 64 КБ памяти, можно эффективно проверить доступность более 512 000 номеров за один проход. - person supercat; 14.06.2013
comment
Если функция скремблирования выбирается случайным образом из набора 2 ^ 32 перестановок, которые эквивалентны случайным распределениям, возможно, что тщательно составленный список будет включать все 512 000 чисел для некоторых из этих распределений, но не очень много. На самом деле, даже с данными, которые были намеренно созданы как злые, выбор действительно случайного выбора один из 2 ^ 32, вероятно, даст шанс на успех выше 99,9999% за один проход. - person supercat; 14.06.2013
comment
Конечно, для этого алгоритма в наихудшем случае числа были созданы тем же генератором случайных чисел, который вы используете. Предполагая, что вы можете гарантировать, что это не так, ваша лучшая тактика - использовать линейный генератор конгруэнтных случайных чисел для генерации вашего списка, чтобы вы проходили через числовое пространство псевдослучайным способом. Это означает, что если вы каким-то образом потерпите неудачу, вы можете продолжать генерировать числа, пока не охватите весь диапазон целых чисел (или не нашли пробел), никогда не дублируя свои усилия. - person Dewi Morgan; 14.09.2015
comment
@DewiMorgan: верно, хотя это действительно худший случай (например, невезение). - person leftaroundabout; 14.09.2015

Если у вас отсутствует одно целое число в диапазоне [0, 2 ^ x - 1], просто исключайте их все вместе. Например:

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

(Я знаю, что это не отвечает на вопрос точно, но это хороший ответ на очень похожий вопрос.)

person rfrankel    schedule 24.08.2011
comment
Да, легко доказать, что [] работает, когда отсутствует одно целое число, но часто не работает, если не хватает более одного. Например, 0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7 равно 0. [ Если записать 2 x для 2-й степени x и 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 всех чисел, кроме одного, является значением пропущенного] - person James Waldby - jwpat7; 24.08.2011
comment
Как я упоминал в комментарии к ответу ircmaxell: проблема не в том, что одно число отсутствует, а в том, чтобы найти число, не включенное в 4 миллиарда чисел в файле. Если мы примем 32-битные целые числа, то в файле может отсутствовать около 300 миллионов чисел. Вероятность совпадения xor представленных чисел с отсутствующим числом составляет всего около 7%. - person James Waldby - jwpat7; 24.08.2011
comment
Это ответ, о котором я думал, когда впервые прочитал вопрос, но при ближайшем рассмотрении я думаю, что вопрос более двусмысленный, чем этот. К вашему сведению, это вопрос, о котором я думал: stackoverflow.com/questions/35185/ - person Lee Netherton; 24.08.2011

Возможно, они захотят узнать, слышали ли вы о вероятностном фильтре Блума, который может очень эффективно определить, абсолютно ли значение не соответствует действительности. часть большого набора (но может определить только с большой вероятностью, что он является членом набора).

person Paul    schedule 23.08.2011
comment
Вероятно, если установлено более 90% возможных значений, вашему фильтру Блума, вероятно, потребуется выродиться в битовое поле, которое уже используется во многих ответах. В противном случае вы просто получите бесполезную полностью заполненную битовую строку. - person Christopher Creutzig; 24.08.2011
comment
@Christopher Как я понимаю фильтры Блума, вы не получите заполненный битовый массив, пока не достигнете 100%. - person Paul; 24.08.2011
comment
... иначе вы получите ложноотрицательные результаты. - person Paul; 24.08.2011
comment
@Paul заполненный битовый массив дает допустимые ложные срабатывания. В этом случае фильтр Блума, скорее всего, выродится к случаю, когда решение, которое было бы отрицательным, возвращает ложное срабатывание. - person ataylor; 24.08.2011
comment
@Paul: вы можете получить заполненный битовый массив, как только количество хэш-функций, умноженное на количество записей, будет равно длине вашего поля. Конечно, это был бы исключительный случай, но вероятность довольно быстро возрастет. - person Christopher Creutzig; 25.08.2011

Исходя из текущей формулировки исходного вопроса, самое простое решение:

Найдите максимальное значение в файле и добавьте к нему 1.

person oosterwal    schedule 23.08.2011
comment
Что, если в файл включен MAXINT? - person Petr Peller; 23.08.2011
comment
@Petr Peller: Библиотека BIGINT по существу снимет ограничения на целочисленный размер. - person oosterwal; 23.08.2011
comment
@oosterwal, если этот ответ был разрешен, вам даже не нужно читать файл - просто выведите как можно большее число. - person Nakilon; 24.08.2011
comment
@Nakilon: Хотя это может сработать почти в каждом отдельном случае, нет никакой гарантии, что случайное огромное число, которое было напечатано, не было в файле. - person oosterwal; 24.08.2011
comment
@oosterwal, если ваше случайное огромное число было самым большим, которое вы могли напечатать, и оно было в файле, то эту задачу нельзя было бы решить. - person Nakilon; 24.08.2011
comment
@Nakilon: +1 Ваша точка зрения принята. Это примерно эквивалентно подсчету общего количества цифр в файле и печати числа с таким количеством цифр. - person oosterwal; 24.08.2011
comment
@oosterwal - есть гарантия, если случайно огромное число было слишком большим для представления в файле такого размера. Я пришел к аналогичному выводу и обсудил его в своем ответе. - person Justin Morgan; 29.05.2012

Используйте 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 BitSet - один для положительных чисел и один для отрицательных чисел, но требования к памяти не меняются.

person dty    schedule 22.08.2011
comment
Или, если вы не хотите использовать BitSet ... попробуйте массив бит. То же самое;) - person jcolebrand; 23.08.2011

Если ограничения по размеру нет, самый быстрый способ - взять длину файла и сгенерировать длину файла + 1 число случайных цифр (или просто «11111 ...»). Преимущество: вам даже не нужно читать файл, и вы можете минимизировать использование памяти почти до нуля. Недостаток: вы напечатаете миллиарды цифр.

Однако, если бы единственным фактором была минимизация использования памяти, а все остальное не важно, это было бы оптимальным решением. Это может даже принести вам награду за "худшее нарушение правил".

person vsz    schedule 24.08.2011

Если мы предположим, что диапазон чисел всегда будет 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 будут работать 2 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;
person ircmaxell    schedule 24.08.2011
comment
Проблема не в том, что одно число отсутствует, а в том, чтобы найти число, не включенное в 4 миллиарда чисел в файле. Если мы примем 32-битные целые числа, то в файле может отсутствовать около 300 миллионов чисел. Вероятность совпадения xor представленных чисел с отсутствующим числом составляет всего около 7%. - person James Waldby - jwpat7; 24.08.2011
comment
Если у вас есть непрерывный диапазон, который не отсчитывается от нуля, но не отсчитывает его от нуля, добавьте вместо xor. sum(0..n) = n*(n+1)/2. Итак missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[]). (Суммарная идея из ответа @hammar.) - person Peter Cordes; 15.08.2015

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

Конечно, вам понадобится план резервного копирования на случай, если n+1 вызовет целочисленное переполнение.

person Mark Ransom    schedule 22.08.2011
comment
Вот решение, которое работает ... кроме случаев, когда это не так. Полезный! :-) - person dty; 23.08.2011
comment
Если он не был процитирован неправильно, вопрос не ограничивал тип целого числа или даже используемый язык. Во многих современных языках целые числа ограничены только доступной памятью. Если наибольшее целое число в файле ›10 МБ, не повезло, во втором случае задача невыполнима. Мое любимое решение. - person Jürgen Strobel; 21.09.2011

Проверьте размер входного файла, затем выведите любое число, которое слишком велико для представления файлом такого размера. Это может показаться дешевым трюком, но это творческое решение проблемы собеседования, оно аккуратно обходит проблему с памятью, и технически это 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 bitcount - 1, который всегда будет больше, чем 2 bitcount. Технически, вам нужно побить число 2 bitcount - (4 * 10 9 - 1), поскольку вы знаете, что есть (4 миллиарда - 1) другие целые числа в файле, и даже при идеальном сжатии они занимают не менее одного бита каждое.

person Justin Morgan    schedule 24.08.2011
comment
Почему бы просто не Console.Write( 1 << bitcount ) вместо цикла? Если в файле n битов, то любое (_n_ + 1) -битное число с ведущей единицей гарантированно будет больше. - person Emmet; 19.11.2013
comment
@Emmet - это просто вызовет целочисленное переполнение, если только файл не будет меньше размера int (4 байта в C #). C ++ может позволить вам использовать что-то большее, но C #, похоже, не позволяет ничего, кроме 32-битных int с оператором <<. В любом случае, если вы не наберете свой собственный гигантский целочисленный тип, это будет очень маленький размер файла. Демо: rextester.com/BLETJ59067 - person Justin Morgan; 20.06.2014

  • Самый простой подход - найти минимальное число в файле и вернуть на 1 меньше этого числа. При этом используется память O (1) и время O (n) для файла из n чисел. Однако это не удастся, если диапазон номеров ограничен, что может сделать min-1 не-числом.

  • О простом и понятном способе использования растрового изображения уже упоминалось. Этот метод использует время и память O (n).

  • Также упоминался двухпроходный метод с 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 возрастает.

person James Waldby - jwpat7    schedule 22.08.2011

Почему-то, как только я прочитал эту задачу, я подумал о диагонализации. Я предполагаю сколь угодно большие целые числа.

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

person Community    schedule 24.08.2011
comment
+1 за креативность (и самый маленький результат наихудшего случая для однопроходного решения). - person hmakholm left over Monica; 24.08.2011
comment
Но нет 4 миллиардов бит для диагонализации, есть только 32. Вы просто получите 32-битное число, которое отличается от первых 32 чисел в списке. - person Brian Gordon; 24.08.2011
comment
@Henning Это вряд ли единственный проход; вам все еще нужно преобразовать из унарного в двоичный. Изменить: ну, я думаю, это один проход по файлу. Ничего. - person Brian Gordon; 24.08.2011
comment
@ Брайан, а где здесь что-то одинарное? Ответ заключается в построении двоичного ответа один бит за раз, и он только один раз читает входной файл, делая его за один проход. (Если требуется вывод decimal, все становится проблематичным - тогда вам, вероятно, лучше построить одну десятичную цифру для трех входных чисел и принять 10% -ное увеличение в журнале выходного числа). - person hmakholm left over Monica; 24.08.2011
comment
@Brian (первый комментарий), Джонатан явно принял произвольно большие целые числа. - person hmakholm left over Monica; 24.08.2011
comment
@Henning Проблема не имеет смысла для произвольно больших целых чисел, потому что, как отметили многие, тривиально просто найти наибольшее число и добавить его или построить очень длинное число из самого файла. Это решение диагонализации особенно неуместно, потому что вместо ветвления по i-му биту вы можете просто вывести 1 бит 4 миллиарда раз и добавить еще 1 в конце. Я в порядке с произвольно большими целыми числами в алгоритме, но я думаю, что проблема в том, чтобы вывести отсутствующее 32-битное целое число. По-другому в этом нет никакого смысла. - person Brian Gordon; 24.08.2011
comment
@Henning (первый комментарий) По какой-то причине я подумал, что выходные значения 2 ^ 32 бит будут иметь какое-то значение как унарное число, преобразованное в 32-битное двоичное число. Это всего лишь подсчет населения: X - person Brian Gordon; 24.08.2011

Вы можете использовать битовые флаги, чтобы отметить, присутствует ли целое число или нет.

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

Предполагая, что каждое целое число является 32-битным, они будут удобно помещаться в 1 ГБ ОЗУ, если выполняется битовая маркировка.

person Shamim Hafiz    schedule 22.08.2011
comment
0,5 Гб, если вы не переопределили байт на 4 бита ;-) - person dty; 23.08.2011
comment
@dty Я думаю, он имеет в виду комфортно, так как в 1Гб будет много места. - person corsiKa; 23.08.2011

Удалите из файла пробелы и нечисловые символы и добавьте 1. Теперь ваш файл содержит единственный номер, не указанный в исходном файле.

Из Reddit, автор: Carbonetc.

person Ashley    schedule 24.08.2011
comment
Любить это! Хотя это не совсем тот ответ, который он искал ...: D - person Johann du Toit; 24.08.2011

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

Пусть все возможные целые числа будут диапазоном от int_min до int_max, а bool isNotInFile(integer) функцией, которая возвращает истину, если файл не содержит определенного целого числа, и ложь иначе (путем сравнения этого определенного целого числа с каждым целым числом в файле)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}
person deg    schedule 24.08.2011
comment
Вопрос касался именно алгоритма функции isNotInFile. Прежде чем отвечать, убедитесь, что вы поняли вопрос. - person Aleks G; 24.08.2011
comment
нет, вопрос заключался в том, какое целое число отсутствует в файле, а не является целым числом x в файле. функция для определения ответа на последний вопрос могла бы, например, просто сравнить каждое целое число в файле с рассматриваемым целым числом и вернуть истину при совпадении. - person deg; 24.08.2011
comment
Думаю, это законный ответ. За исключением ввода-вывода, вам нужно только одно целое число и флаг типа bool. - person Brian Gordon; 24.08.2011
comment
@Aleks G - Я не понимаю, почему это помечено как неправильное. Мы все согласны с тем, что это самый медленный алгоритм из всех :-), но он работает и для чтения файла требуется всего 4 байта. Исходный вопрос не предусматривает, что файл может быть прочитан, например, только один раз. - person Simon Mourier; 24.08.2011
comment
@Simon Я никогда не говорил, что файл нужно прочитать один раз. Я заявил, что вопрос включает требование к алгоритму проверки наличия числа в файле. - person Aleks G; 24.08.2011
comment
@Aleks G - Верно. Я тоже никогда не говорил, что ты это сказал. Мы просто говорим, что IsNotInFile может быть тривиально реализован с использованием цикла для файла: Open; While Not Eof {Read Integer; Return False if Integer = i; Else Continue;}. Ему нужно всего 4 байта памяти. - person Simon Mourier; 24.08.2011

Для ограничения памяти 10 МБ:

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

Когда закончите, просто выберите путь, который не был создан ранее, чтобы создать запрошенный номер.

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

ИЗМЕНИТЬ

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

ИЗМЕНИТЬ II

Нет необходимости строить дерево полностью. Вы должны строить глубокие ответвления только в том случае, если числа похожи. Если мы срежем и ветки, то это решение действительно может сработать.

person Jérôme Verstrynge    schedule 22.08.2011
comment
... и как это поместится в 10 МБ? - person hmakholm left over Monica; 23.08.2011
comment
Как насчет: ограничьте глубину BTree чем-нибудь, что поместится в 10 МБ; это означало бы, что у вас будут результаты в наборе {ложное срабатывание | Positive}, и вы можете пройти через это и использовать другие методы для поиска значений. - person Jonathan Dickinson; 23.08.2011

Отвечу на версию на 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;
    }
}
person BobTurbo    schedule 23.08.2011

Пока мы даем творческие ответы, вот еще один.

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

person Community    schedule 12.07.2013

Удаление битов

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

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 };

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

person Jonathan Dickinson    schedule 23.08.2011

2 128 * 10 18 + 1 (что составляет (2 8) 16 * 10 18 < / sup> +1) - разве это не универсальный ответ на сегодняшний день? Это представляет собой число, которое не может быть сохранено в файле размером 16 EB, который является максимальным размером файла в любой текущей файловой системе.

person Michael Sagalovich    schedule 24.08.2011
comment
А как бы вы распечатали результат? Вы не можете поместить его в файл, а печать на экране займет несколько миллиардов лет. С сегодняшними компьютерами маловероятно время безотказной работы. - person vsz; 29.08.2011
comment
никогда не говорится, что нам нужно где-то напечатать результат, просто «сгенерируйте» его. так что это зависит от того, что вы имеете в виду под генерацией. в любом случае, мой ответ - это всего лишь уловка, чтобы не разрабатывать настоящий алгоритм :) - person Michael Sagalovich; 29.08.2011

Я думаю, что это решенная проблема (см. Выше), но есть интересный случай, о котором следует помнить, потому что его могут спросить:

Если имеется ровно 4294967295 (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";
  }
}
person Syntaera    schedule 24.08.2011
comment
@Nakilon и TheDayTurns указали на это в комментариях к исходному вопросу. - person Brian Gordon; 24.08.2011

Как сказал Райан в основном, отсортируйте файл, а затем перейдите к целым числам, и когда значение пропущено, оно у вас есть :)

РЕДАКТИРОВАТЬ при отрицательном голосовании: OP упомянул, что файл можно отсортировать, поэтому это допустимый метод.

person ratchet freak    schedule 22.08.2011
comment
Одна из важнейших частей заключается в том, что вы должны делать это на ходу, так что вам нужно будет прочитать только один раз. Доступ к физической памяти происходит медленно. - person Ryan Amos; 23.08.2011
comment
@ryan external sort - это в большинстве случаев сортировка слиянием, поэтому при последнем слиянии вы можете выполнить проверку :) - person ratchet freak; 23.08.2011
comment
Если данные на диске, их нужно будет загрузить в память. Это происходит автоматически файловой системой. Если нам нужно найти одно число (иначе в проблеме не говорится), то чтение отсортированного файла по строке за раз является наиболее эффективным методом. Он использует мало памяти и работает не медленнее, чем все остальное - файл необходимо прочитать. - person Tony Ennis; 23.08.2011
comment
Как вы отсортируете 4 миллиарда целых чисел, если у вас всего 1 ГБ памяти? Если вы используете виртуальную память, это займет очень много времени, так как блоки памяти будут выгружаться и выгружаться из физической памяти. - person Klas Lindbäck; 24.08.2011

Если вы не предполагаете 32-битное ограничение, просто верните случайно сгенерированное 64-битное число (или 128-битное, если вы пессимист). Вероятность столкновения 1 in 2^64/(4*10^9) = 4611686018.4 (примерно 1 из 4 миллиардов). Вы будете правы в большинстве случаев!

(Шутка ... вроде.)

person Peter Gibson    schedule 24.08.2011
comment
Я вижу, это уже предлагалось :) голосов за этих людей - person Peter Gibson; 24.08.2011
comment
Парадокс дня рождения делает такое решение не стоящим риска без проверки файла, чтобы увидеть, действительно ли ваше случайное предположение было правильным ответом. (Парадокс дня рождения в этом случае не применяется, но многократный вызов этой функции для генерации новых уникальных значений действительно создает ситуацию парадокса дня рождения.) - person Peter Cordes; 15.08.2015
comment
@PeterCordes Случайно сгенерированные 128-битные числа - это именно то, как работают UUID - они даже упоминают парадокс дня рождения при вычислении вероятности столкновения в Википедии страница UUID - person Peter Gibson; 19.08.2015
comment
Вариант: найти макс в наборе, прибавить 1. - person Phil; 21.10.2015
comment
Я бы быстро отсортировал исходный массив (без дополнительного хранилища), затем прошел бы по массиву и сообщил бы первое «пропущенное» целое число. Выполнено. Ответил на вопрос. - person Level 42; 10.12.2019

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

Размер файла 4 * 109 * 4 байта = 16 ГиБ

В случае 32-битного целого числа без знака

0 <= Number < 2^32
0 <= Number < 4,294,967,296

Предлагаемое мной решение: C ++ без проверки ошибок

#include <vector>
#include <fstream>
#include <iostream>
using namespace std;

int main ()
{
    const long SIZE = 1L << 32;

    std::vector<bool> checker(SIZE, false);

    std::ifstream infile("file.txt");  // TODO: error checking

    unsigned int num = 0;

    while (infile >> num)
    {
        checker[num] = true ;
    }

    infile.close();

    // print missing numbers

    for (long i = 0; i < SIZE; i++)
    {
        if (!checker[i])
            cout << i << endl ;
    }

    return 0;
}

Сложность

  • Пробел ~ 2 32 бит = 2 29 байтов = 2 19 КБ = 2 9 МБ = 1/2 ГБ
  • Время ~ Один проход
  • Полнота ~ Да
person Community    schedule 14.05.2015
comment
Это дублирует все ответы на растровые изображения, полученные много лет назад. Кроме того, std::vector<bool> не имеет быстрого способа поиска неустановленного бита. Впрочем, std::bitset тоже. (Тестирование 64 бит за раз против (long) -1 на способ быстрее, если только компилятор действительно не умен и не видит поэтапный цикл.) - person Peter Cordes; 15.08.2015
comment
Проверено на x86; gcc 4.9.2 генерирует ужасные побитовые циклы. clang генерирует худшие циклы для установки последовательности битов, но немного лучшие циклы (с использованием bt r, r) для тестирования по частям. Оба они по-прежнему в ~ 100 раз медленнее, чем проверка 64 бита за раз с cmp r, -1 - person Peter Cordes; 15.08.2015

Возможно, я полностью упускаю суть этого вопроса, но вы хотите найти целое число, отсутствующее в отсортированном файле целых чисел?

Эээ ... правда? Давайте подумаем, как бы выглядел такой файл:

1 2 3 4 5 6 ... первое отсутствующее число ... и т. Д.

Решение этой проблемы кажется тривиальным.

person hacksoncode    schedule 24.08.2011
comment
Не указано для сортировки. - person George; 24.08.2011

Вам не нужно их сортировать, просто несколько раз разбивайте их на части.

Первый шаг похож на первый проход быстрой сортировки. Выберите одно из целых чисел x и с его помощью выполните проход по массиву, чтобы поместить все значения меньше x слева от него и значения больше x справа. Найдите, на какой стороне x больше всего доступных слотов (целых чисел нет в списке). Это легко вычислить, сравнив значение x с его положением. Затем повторите разделение в подсписке с этой стороны от x. Затем повторите разделение в подподсписок с наибольшим количеством доступных целых чисел и т. Д. Общее количество сравнений, чтобы перейти к пустому диапазону, должно быть около 4 миллиардов, плюс-минус.

person Community    schedule 25.08.2011

Вы можете ускорить поиск отсутствующих целых чисел после чтения существующих, сохранив диапазоны непосещенных целых чисел в некоторой древовидной структуре.

Вы должны начать с сохранения [0..4294967295] и каждый раз, когда вы читаете целое число, вы объединяете диапазон, в который оно попадает, удаляя диапазон, когда он становится пустым. В конце у вас есть точный набор целых чисел, отсутствующих в диапазонах. Итак, если вы видите 5 как первое целое число, у вас будут [0..4] и [6..4294967295].

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

Одним из способов хранения такого дерева было бы B-дерево с началом диапазона в качестве ключа и концом диапазона в качестве значения. В худшем случае использование будет, когда вы получите все нечетные или четные целые числа, что будет означать хранение 2 ^ 31 значений или десятков ГБ для дерева ... Ой. В лучшем случае это отсортированный файл, в котором для всего дерева нужно использовать только несколько целых чисел.

Так что не совсем правильный ответ, но я подумал, что упомяну этот способ. Полагаю, я бы провалил собеседование ;-)

person Community    schedule 28.09.2011

Возможно, я читаю это слишком внимательно, но в вопросе говорится: «генерировать целое число, которого нет в файле». Я просто отсортирую список и добавлю 1 к максимальной записи. Бам, целое число, которого нет в файле.

person Sib    schedule 24.08.2011
comment
Зачем вам сортировать, если вы просто хотите найти максимум? - person rfrankel; 24.08.2011
comment
Как бы вы отсортировали 4 миллиарда целых чисел, используя не более 1 ГБ памяти? - person Klas Lindbäck; 24.08.2011
comment
@ Брайан лол. Что касается вопросов на собеседовании, то непонимание того, в чем заключается сложность, обычно хуже, чем отсутствие хорошего решения. В конце концов, понимание проблемы - это обычно больше, чем половина пути к ее решению. - person Klas Lindbäck; 24.08.2011

Я придумал следующий алгоритм.

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

Например, предположим, что целое число 32-битное, тогда нам потребуется

int[] ones = new int[32];
int[] zeroes = new int[32];

Для каждого числа мы должны перебрать 32 бита и увеличить значение 0 или 1:

for(int i = 0; i < 32; i++){
   ones[i] += (val>>i&0x1); 
   zeroes[i] += (val>>i&0x1)==1?0:1;
}

Наконец, после обработки файла:

int res = 0;
for(int i = 0; i < 32; i++){
   if(ones[i] < (long)1<<31)res|=1<<i;
}
return res;

ПРИМЕЧАНИЕ: в некоторых языках (например, Java) 1 ‹---------------- 31 - отрицательное число, поэтому (длинное) 1 ‹› 31 - правильный способ сделать это.

person Community    schedule 06.02.2012
comment
Это одно из решений, которое работает только с одним отсутствующим целым числом и без дубликатов (то есть есть только один возможный ответ)? Если это так, похоже, он работает по той же причине, что и менее трудоемкий метод xor. - person Peter Cordes; 15.08.2015

Старый вопрос, но меня интересуют «нефункциональные» требования. На мой взгляд, здесь должна быть подсказка - если этот вопрос был задан где-то еще, а не в книге, в которой затем обсуждаются все возможности с плюсами и минусами. Достаточно часто кажется, что это вопросы на собеседовании, оставляя меня озадаченным, поскольку нельзя дать однозначный ответ, не зная мягких требований, то есть «поиск недостающих чисел должен быть очень быстрым, потому что он используется x раз в секунду. ".

Думаю, на такой вопрос можно было бы дать разумный ответ.

  • Я бы слил-отсортил все числа в новый файл, используя 4 байта на int. Конечно, поначалу это будет происходить медленно. Но это можно сделать с небольшим объемом памяти (необязательно хранить все в ОЗУ).
  • Используйте двоичный поиск, чтобы проверить, существует ли номер в предварительно отсортированном файле. Поскольку мы остаемся 4 байтами на значение, это не проблема

недостатки:

  • Размер файла
  • Медленная первая сортировка - но требуется только один раз

преимущества:

  • очень быстро искать

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

person Community    schedule 06.10.2013
comment
Как вы думаете, почему интервьюер может ожидать единственного наилучшего решения? Также вы решаете проблему, отличную от той, о которой спрашивали: речь идет не о проверке наличия в файле некоторого заданного числа, а о создании такого числа, которое не там встречается. - person hmakholm left over Monica; 13.12.2013

Конечно, имея ограниченный опыт (только что начал изучать java в Uni), вы можете запустить хотя бы один набор (бочонок) int, а если число не найдено, избавьтесь от бочки. Это освободит место и запустит проверку каждой единицы данных. Если то, что вы ищете, будет найдено, добавьте его в счетную переменную. Это займет много времени, но если вы создадите несколько переменных для каждого раздела и проведете счетчик проверок для каждой переменной и убедитесь, что они выходят / удаляются одновременно, хранилище переменных не должно увеличиваться? И ускорит процесс проверки. Просто мысль.

person Community    schedule 15.12.2014