Найти целое число не среди четырех миллиардов заданных - PullRequest
675 голосов
/ 23 августа 2011

Это вопрос интервью:

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

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

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


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

Ответы [ 38 ]

17 голосов
/ 23 августа 2011

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

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

16 голосов
/ 24 августа 2011

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

14 голосов
/ 23 августа 2011

Используйте 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 - один для номеров + и один для номеров - но требования к памяти не меняются.

12 голосов
/ 24 августа 2011

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

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

10 голосов
/ 24 августа 2011

Если мы предположим, что диапазон чисел всегда будет равен 2 ^ n (четная степень 2), то будет работать исключающее-или (как показано на другом плакате).Насколько это так, давайте докажем это:

Теория

Учитывая любой целочисленный диапазон на основе 0, в котором есть 2^n элементов с одним отсутствующим элементом, вы можете найти этот отсутствующий элемент просто путем xorобъединение известных значений для получения пропущенного числа.

Доказательство

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

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

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

Этот алгоритм будет работать для диапазонов от любого начального числа до любого конечного числа, покаобщий диапазон равен 2 ^ n ... Это в основном переопределяет диапазон, чтобы иметь минимум в 0. Но это действительно требует 2 прохода через файл (первый, чтобы получить минимум, второй, чтобы вычислить отсутствующее целое число).

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;
9 голосов
/ 24 августа 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)другие целые числа в файле, и даже при идеальном сжатии они будут занимать по крайней мере один бит каждый.

8 голосов
/ 23 августа 2011
  • Самый простой подход - найти минимальное число в файле и вернуть на 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'ования всех чисел, упомянутый до сих пор rfrankel и в длину ircmaxell, отвечает на вопрос, заданный в stackoverflow # 35185 , как указал ltn100. Он использует O (1) хранилище и O (n) время выполнения. Если на данный момент мы предполагаем 32-разрядные целые числа, XOR имеет 7% вероятности получения различного числа. Обоснование: дано ~ 4G различных чисел XOR'd вместе, и ок. 300M отсутствует в файле, число установленных битов в каждой битовой позиции имеет равную вероятность быть нечетным или четным. Таким образом, 2 ^ 32 числа имеют равную вероятность возникновения как результат XOR, из которых 93% уже находятся в файле. Обратите внимание, что если числа в файле не все различны, вероятность успеха метода XOR возрастает.

8 голосов
/ 23 августа 2011

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

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

7 голосов
/ 24 августа 2011

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

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

6 голосов
/ 24 августа 2011

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

Из Reddit от Carbonetc.

...