Самый эффективный способ хранения тысяч телефонных номеров

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

Существует около тысячи телефонных номеров, каждый из которых имеет 10 цифр. Вы можете предположить, что первые 5 цифр каждого будут одинаковыми по тысячам чисел. Вам необходимо выполнить следующие операции: а. Найдите, существует ли данный номер. б. Распечатайте все номера

Каков наиболее эффективный способ экономии пространства?

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

Можно ли использовать суффикс trie help?

В идеале 1000 сохраненных номеров занимают 4 байта на каждый номер, поэтому для хранения 1000 номеров потребуется 4000 байт. Количественно я хочу уменьшить объем хранилища до < 4000 байтов, это объяснил мне мой интервьюер.

Ответ 1

Здесь улучшен ответ aix. Рассмотрите возможность использования трех "слоев" для структуры данных: первая - это константа для первых пяти цифр (17 бит); поэтому отсюда каждый номер телефона имеет только оставшиеся пять цифр. Мы рассматриваем эти оставшиеся пять цифр как 17-битные двоичные целые числа и сохраняем k этих битов одним методом и 17 - k = m с другим методом, определяя k в конце, чтобы минимизировать требуемое пространство.

Сначала сортируем номера телефонов (все сокращены до 5 десятичных цифр). Затем мы подсчитываем количество телефонных номеров, для которых двоичное число, состоящее из первых m бит, равно 0, для количества телефонных номеров первые m бит не более 0... 01, для количества телефонных номеров первый м биты не более 0... 10 и т.д., до количества телефонных номеров, для которых первые m бит равны 1... 11 - этот последний счет 1000 (десятичный). Существует 2 ^ m таких подсчетов, и каждое число не более 1000. Если мы опустим последний (поскольку в любом случае мы знаем, что оно равно 1000), мы можем сохранить все эти числа в смежном блоке (2 ^ m - 1) * 10 бит. (10 бит достаточно для хранения числа меньше 1024.)

Последние k бит всех (сокращенных) телефонных номеров хранятся смежно в памяти; поэтому, если k, скажем, 7, то первые 7 бит этого блока памяти (биты 0 - 6) соответствуют последним 7 битам первого (уменьшенного) номера телефона, биты с 7 по 13 соответствуют последним 7 битам второго (сокращенного) номера телефона и т.д. Для этого требуется 1000 * k бит для всего 17 + (2 ^ (17 - k) - 1) * 10 + 1000 * k, что достигает минимума 11287 при k = 10. Таким образом, мы можем хранить все телефонные номера в потолке ( 11287/8) = 1411 байт.

Дополнительное пространство можно сохранить, заметив, что ни один из наших номеров не может начинаться, например. 1111111 (двоичный), потому что самое младшее число, которое начинается с этого, равно 130048, и у нас есть только пять десятичных цифр. Это позволяет нам сбрить несколько записей с первого блока памяти: вместо 2 ^ m - 1 отсчетов нам нужен только ceil (99999/2 ^ k). Это означает, что формула становится

17 + ceil (99999/2 ^ k) * 10 + 1000 * k

который удивительно достигает своего минимума 10997 для k = 9 и k = 10, или для ceil (10997/8) = 1375 байт.

Если мы хотим узнать, есть ли в нашем наборе определенный номер телефона, мы сначала проверяем, соответствуют ли первые пять двоичных цифр пятизначным цифрам. Затем мы разделим оставшиеся пять цифр на верхние m = 7 бит (это, скажем, m-разрядное число M) и его нижние k = 10 бит (число K). Теперь мы найдем номер a [M-1] сокращенных телефонных номеров, для которых первые m цифр не более M - 1, а число a [M] сокращенных номеров телефонов, для которых первые m цифр не более M, как из первого блока бит. Теперь мы проверим между a [M-1] th и [M] -ой последовательностью k бит во втором блоке памяти, чтобы увидеть, найдем ли мы K; в худшем случае существует 1000 таких последовательностей, поэтому, если мы используем двоичный поиск, мы можем завершить операции O (log 1000).

Ниже следует псевдокод для печати всех 1000 номеров, где я получаю доступ к K-бит-битному входу первого блока памяти в качестве [K] и M-м-бит-записи второго блока памяти как b [M] (для обоих из них потребуется несколько бит операций, которые утомительны для записи). Первые пять цифр находятся в числе с.

i := 0;
for K from 0 to ceil(99999 / 2^k) do
  while i < a[K] do
    print(c * 10^5 + K * 2^k + b[i]);
    i := i + 1;
  end do;
end do;

Может быть, что-то пойдет не так с граничным случаем для K = ceil (99999/2 ^ k), но это достаточно легко исправить.

Наконец, с энтропийной точки зрения невозможно сохранить подмножество из 10 ^ 3 положительных целых чисел менее 10 ^ 5 меньше, чем ceil (log [2] (биномиальный (10 ^ 5, 10 ^ 3))) = 8073. В том числе 17 для первых 5 цифр, есть еще разрыв 10997 - 8090 = 2907 бит. Это интересная задача, чтобы увидеть, есть ли лучшие решения, где вы можете получить доступ к номерам относительно эффективно!

Ответ 2

В дальнейшем я обрабатываю числа как целые переменные (в отличие от строк):

  • Сортировка номеров.
  • Разделите каждое число на первые пять цифр и последние пять цифр.
  • Первые пять цифр одинаковы по номерам, поэтому сохраняйте их только один раз. Для этого потребуется 17 бит памяти.
  • Сохраняйте последние пять цифр каждого номера отдельно. Это потребует 17 бит на номер.

Для повторения: первые 17 бит являются общим префиксом, последующие 1000 групп из 17 бит - это последние пять цифр каждого номера, хранящихся в порядке возрастания.

В целом мы рассматриваем 2128 байт для 1000 номеров или 17,017 бит на 10-значный номер телефона.

Поиск O(log n) (двоичный поиск) и полное перечисление O(n).

Ответ 4

Я бы, вероятно, подумал об использовании некоторой сжатой версии Trie (возможно, DAWG, как было предложено @Misha).

Это автоматически воспользовалось бы тем фактом, что все они имеют общий префикс.

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

Ответ 5

Я слышал об этой проблеме раньше (но без первых пяти цифр - это одно и то же предположение), и самый простой способ сделать это был Rice Кодирование:

1) Поскольку порядок не имеет значения, мы можем сортировать их и сохранять только различия между последовательными значениями. В нашем случае средние различия будут составлять 100 000/1000 = 100

2) Кодировать различия с использованием кодов Риса (база 128 или 64) или даже кодов Голомба (база 100).

EDIT: Оценка для кодирования Rice с базой 128 (не потому, что она даст наилучшие результаты, а потому, что ее легче вычислить):

Мы сохраним первое значение as-is (32 бит).
Остальные значения 999 являются отличиями (мы ожидаем, что они будут небольшими, в среднем 100) будут содержать:

унарное значение value / 128 (переменное число бит + 1 бит в качестве терминатора)
двоичное значение для value % 128 (7 бит)

Нам нужно как-то оценить пределы (пусть назовем его VBL) для числа переменных битов:
нижний предел: подумайте, что нам повезло, и разница не больше нашей базы (в этом случае 128). это означало бы дать 0 дополнительных бит.
верхний предел: поскольку все различия, меньшие, чем базовые, будут закодированы в двоичной части числа, максимальное число, которое нам нужно будет кодировать в унарном, равно 100000/128 = 781,25 (тем более, потому что мы не ожидаем, что большая часть различий будет равна нулю).

Итак, в результате получается 32 + 999 * (1 + 7) + переменная (0..782) bits = 1003 + variable (0..98) bytes.

Ответ 6

Это хорошо известная проблема от Bentley Programming Pearls.

Решение: Удалите первые пять цифр из чисел, поскольку они одинаковы для каждого номер. Затем используйте побитовые операции для представления оставшихся 9999 возможных стоимость. Для представления чисел вам понадобятся только 2 ^ 17 бит. Каждый бит представляет собой число. Если бит установлен, номер находится в телефонной книге.

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

Вы можете искать число в O (1), а эффективность пространства максимальна из-за представления бит.

HTH Chris.

Ответ 7

Фиксированное хранение 1073 байта для 1000 номеров:

Основным форматом этого метода хранения является сохранение первых 5 цифр, подсчет для каждой группы и смещение для каждого номера в каждой группе.

Префикс:
Наш пятизначный префикс занимает первое 17 бит.

Группировка:
Затем нам нужно выяснить, какая группа подходит для чисел. Попробуйте иметь около 1 номера на группу. Поскольку мы знаем, что в магазине хранится около 1000 номеров, мы делим 99999 на 1000 частей. Если мы выберем размер группы как 100, будут потеряны биты, поэтому попробуйте размер группы 128, которые могут быть представлены 7 битами. Это дает нам 782 группы для работы.

Графы:
Далее, для каждой из 782 групп нам нужно сохранить количество записей в каждой группе. 7-битное число для каждой группы даст 7*782=5,474 bits, что очень неэффективно, потому что среднее число составляет около 1 из-за того, как мы выбрали наши группы.

Таким образом, вместо этого мы имеем счетчики с переменным размером с ведущим 1 для каждого числа в группе, за которым следует 0. Таким образом, если бы мы имели x числа в группе, мы имели бы x 1's, за которым следует a 0 для представления счета. Например, если бы у нас было 5 чисел в группе, счетчик был бы представлен 111110. С помощью этого метода, если число 1000 чисел, мы получим 1000 1 и 782 0 для общей суммы 1000 + 782 = 1,782 бит для подсчета.

Offset:
Наконец, формат каждого номера будет всего лишь 7-битным смещением для каждой группы. Например, если 00000 и 00001 являются единственными числами в группе 0-127, биты для этой группы будут 110 0000000 0000001. Предполагая 1000 номеров, для смещений будет 7 000 бит.

Таким образом, наш окончательный счет, предполагающий 1000 номеров, выглядит следующим образом:

17 (prefix) + 1,782 (counts) + 7,000 (offsets) = 8,799 bits = 1100 bytes

Теперь давайте посмотрим, лучший выбор для размера группы - это выбор размера группы размером до 128 бит. Выбирая x как число бит для представления каждой группы, формула для размера:

Size in bits = 17 (prefix) + 1,000 + 99,999/2^x + x * 1,000

Сведение к минимуму этого уравнения для целых значений x дает x=6, что дает 8 580 бит = 1,073 байта. Таким образом, наше идеальное хранилище выглядит следующим образом:

  • Размер группы: 2 ^ 6 = 64
  • Количество групп: 1,562
  • Общая память:

    1017 (prefix plus 1's) + 1563 (0 in count) + 6*1000 (offsets) = 8,580 bits = 1,073 bytes

Ответ 8

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

Проблемы с этим состоят в том, что вам придется представлять каждый из наборов 2 ^ 8000 в вашей программе как lut, и даже Google не сможет удаленно выполнить это.

Поиск будет O (1), распечатывая все число O (n). Вставка будет O (2 ^ 8000), которая теоретически является O (1), но на практике непригодна.

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

EDIT: Хорошо, вот одна "реализация".

Шаги по созданию реализации:

  • Возьмите постоянный массив размером 100 000 * (1000 выберите 100 000) бит. Да, я знаю, что этот массив понадобится больше пространства, чем атомы во Вселенной, на несколько величин.
  • Отделите этот большой массив на куски по 100 000 каждый.
  • В каждом блоке хранится бит массива для одной конкретной комбинации последних пяти цифр.

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

Вот как использовать этот LUT:

  • Когда кто-то дает вам 1000 номеров, вы сохраняете первые пять цифр отдельно.
  • Узнайте, какой из блоков вашего массива соответствует этому набору.
  • Сохраните номер набора в одном номере 8074 бит (вызовите это c).

Это означает, что для хранения нам нужны только 8091 бит, которые мы доказали здесь как оптимальное кодирование. Однако поиск правильного фрагмента требует O (100 000 * (100 000 выбрать 1000)), который согласно математическим правилам равен O (1), но на практике всегда будет занимать больше времени, чем время Вселенной.

Поиск просто:

  • полоса первых пяти цифр (оставшееся число будет называться n ').
  • проверьте, соответствуют ли они
  • Вычислить я = c * 100000 + n '
  • Убедитесь, что бит в я в LUT установлен на один

Печать всех чисел также проста (и фактически принимает O (100000) = O (1), потому что вам всегда нужно проверять все биты текущего фрагмента, поэтому я просчитал это выше).

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

Ответ 9

Это эквивалентно хранению тысячи неотрицательных целых чисел, каждая из которых меньше 100 000. Для этого мы можем использовать что-то вроде арифметического кодирования.

В конечном итоге номера будут сохранены в отсортированном списке. Я отмечаю, что ожидаемая разница между соседними номерами в списке составляет 100 000/1000 = 100, которые могут быть представлены в 7 бит. Также будет много случаев, когда требуется более 7 бит. Простым способом представления этих менее распространенных случаев является принятие схемы utf-8, где один байт представляет собой 7-битовое целое число, если первый бит не установлен, и в этом случае следующий байт считывается для создания 14-битного целого числа, если только его первый бит установлен, и в этом случае следующий байт считывается для представления 21-битного целого числа.

Таким образом, по крайней мере половина различий между последовательными целыми числами может быть представлена ​​одним байтом, а почти всем остальным требуется два байта. Несколько номеров, разделенных большими различиями, чем 16384, потребуют трех байтов, но их не может быть больше 61. Среднее хранилище будет составлять около 12 бит на номер, или бит меньше, или не более 1500 байт.

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

После написания, я заметил, что ruslik уже предложил метод разницы выше, единственная разница - это схема кодирования. Шахта, вероятно, более простая, но менее эффективная.

Ответ 10

Просто быстро спросите любую причину, по которой мы не хотим менять числа в базу 36. Это может не сэкономить столько места, но это наверняка сэкономит время на поиск, так как u будет смотреть на гораздо меньше 10digts. Или я разделил бы их на файлы в зависимости от каждой группы. поэтому я бы назвал файл (111) -222.txt, а затем я бы только сохранил числа, которые вписываются в эту группу, а затем их seearchable в числовом порядке таким образом, я всегда могу взломать, чтобы посмотреть, выходит ли файл. перед тем, как запустить более сильный поиск. или, быть верным, я буду запускать двоичные поисковые запросы для файла, чтобы увидеть, выходит ли он. и еще один быстрый поиск по содержимому файла

Ответ 11

Почему бы не сохранить его простым? Используйте массив структур.

Итак, мы можем сохранить первые 5 цифр как константу, поэтому забудьте об этом пока.

65535 - это максимум, который может быть сохранен в 16-битном номере, а максимальное число, которое у нас может быть, - 99999, которое соответствует 17-му биту с максимальным числом 131071.

Использование 32-битных типов данных - это wast, потому что нам нужен только 1 бит дополнительных 16 бит... поэтому мы можем определить структуру с булевым (или символом) и 16-битным числом.

Предполагая, что C/С++

typedef struct _number {

    uint16_t number;
    bool overflow;
}Number;

Эта структура занимает всего 3 байта, и нам нужен массив из 1000, а всего 3000 байт. Мы сократили общее пространство на 25%!

Что касается сохранения чисел, мы можем сделать простую побитную математику

overflow = (number5digits & 0x10000) >> 4;
number = number5digits & 0x1111;

И обратный

//Something like this should work
number5digits = number | (overflow << 4);

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

for(int i=0;i<1000;i++) cout << const5digits << number5digits << endl;

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

Ответ 12

Мое решение: лучший случай 7.025 бит/число, наихудший случай 14.193 бит/число, грубая средняя 8.551 бит/число. Stream-encoded, без произвольного доступа.

Еще до того, как я прочитал rusliks, я сразу подумал о кодировании разницы между каждым номером, так как он будет небольшим и должен быть относительно последовательным, но решение также должно быть в состоянии удовлетворить наихудший сценарий. У нас есть пространство в 100000 номеров, содержащих только 1000 номеров. В совершенно единой телефонной книге каждое число будет больше, чем предыдущее число на 100:

55555-12 3 45
55555-12 4 45
55555-12 5 45

Если это так, для кодирования различий между числами требуется нулевое хранилище, так как его известная константа. К сожалению, цифры могут отличаться от идеальных шагов 100. Я бы кодировал разницу от идеального приращения 100, так что, если два соседних числа будут отличаться на 103, я буду кодировать число 3, и если два соседних числа будут отличаться на 92, я будет кодировать -8. Я называю дельту от идеального приращения 100 " дисперсия".

Отклонение может варьироваться от -99 (то есть двух последовательных номеров) до 99000 (вся телефонная книга состоит из цифр 00000... 00999 и дополнительного удаленного номера 99999), который представляет собой диапазон в 99100 возможных значений.

Id стремится выделить минимальное хранилище для кодирования наиболее распространенных различий и расширения хранилища, если я столкнусь с большими различиями (например, ProtoBuf s varint). Я использую куски семи бит, шесть для хранения и дополнительный бит в конце, чтобы указать, что эта дисперсия хранится с дополнительным куском после текущего, максимум до трех кусков (что обеспечит максимум 3 * 6 = 18 бит памяти, которые имеют 262144 возможное значение, больше, чем количество возможных дисперсий (99100). Каждый дополнительный фрагмент, следующий за поднятым флагом, имеет бит более высокого значения, поэтому в первом фрагменте всегда есть биты 0-5, дополнительные второстепенные куски имеют биты 6-11, а дополнительный третий блок имеет биты 12-17.

Один блок предоставляет шесть бит памяти, которые могут принимать 64 значения. Id нравится сопоставлять 64 наименьших отклонения, чтобы вписаться в один кусок (т.е. От -32 до +31), поэтому я использую кодировку ProtoBuf ZigZag, вплоть до от -99 до +98 (поскольку нет необходимости в отрицательной дисперсии выше -99), после чего Ill переключается на обычное кодирование, смещение на 98:

 Variance  |  Encoded Value
-----------+----------------
    0      |       0
   -1      |       1
    1      |       2
   -2      |       3
    2      |       4
   -3      |       5
    3      |       6
   ...     |      ...
  -31      |      61
   31      |      62
  -32      |      63
-----------|--------------- 6 bits
   32      |      64
  -33      |      65
   33      |      66
   ...     |      ...
  -98      |      195
   98      |      196
  -99      |      197
-----------|--------------- End of ZigZag
   100     |      198
   101     |      199
   ...     |      ...
  3996     |     4094
  3997     |     4095
-----------|--------------- 12 bits
  3998     |     4096
  3999     |     4097
   ...     |      ...
 262045    |    262143
-----------|--------------- 18 bits

Некоторые примеры того, как отклонения будут закодированы как биты, включая флаг для указания дополнительного фрагмента:

 Variance  |  Encoded Bits
-----------+----------------
     0     |  000000 0
     5     |  001010 0
    -8     |  001111 0
   -32     |  111111 0
    32     |  000000 1  000001 0
   -99     |  000101 1  000011 0
   177     |  010011 1  000100 0
 14444     |  001110 1  100011 1  000011 0

Итак, первые три номера примерной телефонной книги будут закодированы как поток бит следующим образом:

BIN 000101001011001000100110010000011001   000110 1     010110 1 00001 0
PH#           55555-12345                 55555-12448     55555-12491
POS                1                           2               3

Лучший сценарий, телефонная книга несколько равномерно распределена, и нет двух телефонных номеров с разницей больше 32, поэтому для запуска будет использоваться 7 бит на номер плюс 32 бита число в общей сложности 32 + 7 * 999 = 7025 бит.
Смешанный сценарий, где дисперсия 800 номеров телефонов помещается в один кусок (800 * 7 = 5600), 180 номеров соответствуют двум кускам каждый (180 * 2 * 7 = 2520) и 19 номеров, три куска (20 * 3 * 7 = 399) плюс начальные 32 бита, итоговые значения 8551 бит.
Наихудший сценарий, 25 номеров соответствуют трем кускам (25 * 3 * 7 = 525 бит), а остальные 974 номера соответствуют двум фрагментам (974 * 2 * 7 = 13636 бит) плюс 32 бита для первого числа для общей суммы 14193 бит.

   Amount of encoded numbers   |
 1-chunk | 2-chunks | 3-chunks | Total bits
---------+----------+----------+------------
   999   |    0     |    0     |   7025
   800   |   180    |    19    |   8551
    0    |   974    |    25    |  14193

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

  • Третий кусок не нуждается в полных семи битах, он может быть всего пятью битами и без бит флага.
  • Может быть начальный проход чисел для расчета наилучших размеров для каждого фрагмента. Возможно, для определенной телефонной книги было бы оптимально, чтобы первый кусок имел 5 + 1 бит, второй 7 + 1 и третий 5 + 1. Это еще больше уменьшит размер до минимума 6 * 999 + 32 = 6026 бит плюс два набора из трех бит для хранения размеров блоков 1 и 2 (размер блока 3s - оставшаяся часть необходимых 16 бит) для общего из 6032 бит!
  • Тот же самый начальный проход может рассчитывать более ожидаемое приращение, чем значение по умолчанию 100. Возможно, есть телефонная книга, которая начинается с 55555-50000, и поэтому она имеет половину диапазона номеров, поэтому ожидаемый прирост должен составлять 50. Или, может быть, есть возможно нелинейное распределение (возможно, стандартное отклонение) и некоторое другое оптимальное ожидаемое приращение. Это уменьшит типичную дисперсию и может позволить использовать еще меньший первый кусок.
  • Дальнейший анализ может быть выполнен в первом проходе, чтобы разрешить разделение телефонной книги, причем каждый раздел имел свои собственные ожидаемые приращения и оптимизацию размера блока. Это позволило бы уменьшить размер первого блока для некоторых очень однородных частей телефонной книги (уменьшив количество потребляемых битов) и больших размеров кусков для неравномерных частей (уменьшив количество бит, потраченных впустую на флаги продолжения).

Ответ 13

Реальный вопрос заключается в сохранении пятизначных телефонных номеров.

Фокус в том, что вам понадобится 17 бит для хранения диапазона чисел от 0 до 99,999. Но сохранение 17 бит на обычных 8-байтовых границах слов - это хлопот. Вот почему они спрашивают, можете ли вы сделать менее 4k, не используя 32-битные целые числа.

Вопрос: возможны ли все числовые комбинации?

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

Вопрос: будет ли этот список статическим или ему потребуется поддержка обновлений?

Если это статично, тогда, когда приходит время для заполнения базы данных, подсчитывайте количество цифр < 50 000 и количество цифp >= 50 000. Выделите два массива uint16 соответствующей длины: один для целых чисел ниже 50 000 и один для более высокого набора. При хранении целых чисел в более высоком массиве вычитайте 50 000 и при чтении целых чисел из этого массива добавьте 50 000. Теперь вы сохранили свои 1000 целых чисел в 2000 8-байтных словах.

Построение телефонной книги потребует двух входных обходов, но поиск должен происходить в половине случаев, в среднем, по сравнению с одним массивом. Если бы время поиска было очень важным, вы могли бы использовать больше массивов для меньших диапазонов, но я думаю, что при таких размерах ваша производительность связана с вытаскиванием массивов из памяти, а 2k, вероятно, запихивается в кеш процессора, если вы не регистрируете место на чем-либо, что вы будете использовать дни.

Если он динамический, выделите один массив из 1000 или около того uint16 и добавьте числа в отсортированном порядке. Установите первый байт в 50,001 и установите второй байт в соответствующее нулевое значение, например NULL или 65 000. Когда вы храните номера, храните их в отсортированном порядке. Если число ниже 50,001, то сохраните его перед маркером 50,001. Если число равно 50 001 или больше, сохраните его после маркера 50,001, но вычтите 50 000 из сохраненного значения.

Ваш массив будет выглядеть примерно так:

00001 = 00001
12345 = 12345
50001 = reserved
00001 = 50001
12345 = 62345
65000 = end-of-list

Итак, когда вы просматриваете номер в телефонной книге, вы будете перемещаться по массиву, и если вы нажмете на значение 50,001, вы начнете добавлять 50 000 к вашим значениям массива.

Это делает вставки очень дорогими, но поиск проще, и вы не собираетесь тратить на хранение больше 2 тыс..