Один из моих друзей задал этот вопрос интервью -
«Существует постоянный поток чисел, поступающих из некоторого бесконечного списка чисел, из которого вам необходимо поддерживать структуру данных, чтобы возвращать первые 100 старших чисел в любой заданный момент времени. Предположим, что все числа являются только целыми числами».
Это просто, вам нужно держать отсортированный список в порядке убывания и отслеживать наименьшее число в этом списке. Если полученный новый номер больше, чем этот самый низкий номер, то вам нужно удалить этот самый низкий номер и вставить новый номер в отсортированный список по мере необходимости.
Тогда вопрос был расширен -
«Можете ли вы убедиться, что ордер на вставку должен быть O (1)? Возможно ли это?»
Насколько я знаю, даже если вы добавите новый номер в список и снова отсортируете его, используя любой алгоритм сортировки, лучше всего будет O (logn) для быстрой сортировки (я думаю). Так что мой друг сказал, что это невозможно. Но он не был убежден, он попросил сохранить любую другую структуру данных, а не список.
Я думал о сбалансированном бинарном дереве, но даже там вы не получите вставку с порядком 1. Так что тот же вопрос у меня тоже сейчас. Хотел узнать, существует ли какая-либо такая структура данных, которая может выполнять вставку в порядке 1 для вышеуказанной проблемы, или это вообще невозможно.
Ответы:
Допустим, k - это число старших чисел, которые вы хотите знать (100 в вашем примере). Затем вы можете добавить новый номер, в
O(k)
котором такжеO(1)
. Потому чтоO(k*g) = O(g) if k is not zero and constant
.источник
N
размер отсортированного списка или количество обработанных элементов? Если вы обрабатываете 10000 элементов и сохраняете 100 лучших элементов в списке или обрабатываете 1000000000 элементов и сохраняете 100 лучших элементов в отсортированном списке, затраты на вставку в этом списке остаются прежними.O(k*g) = O(g) if k not zero and constant
. =>O(50*1) = O(1)
.Держите список несортированным. Выяснение того, вставлять или нет новый номер, займет больше времени, но вставка будет O (1).
источник
Это просто. Размер списка постоянен, поэтому время сортировки списка постоянно. Операция, которая выполняется в постоянное время, называется O (1). Поэтому сортировка списка - это O (1) для списка фиксированного размера.
источник
После того, как вы передадите 100 номеров, максимальная цена, которую вы когда-либо понесете за следующее число, - это стоимость проверки того, находится ли число в самых высоких 100 числах (давайте пометим это CheckTime ) плюс стоимость, чтобы ввести его в этот набор и извлечь наименьший (назовем это EnterTime ), который является постоянным временем (по крайней мере, для ограниченных чисел), или O (1) .
Затем, если распределение чисел является случайным, средняя стоимость уменьшается, чем больше у вас чисел. Например, вероятность того, что вам нужно будет ввести 101-й номер в максимальный набор, составляет 100/101, шансы на 1000-й номер будут 1/10, а шансы для n-го номера будут 100 / n. Таким образом, наше уравнение для средней стоимости будет:
Таким образом, когда n приближается к бесконечности, важен только CheckTime :
Если числа связаны, CheckTime является постоянным, и, следовательно, это O (1) время.
Если числа не связаны, время проверки будет расти с увеличением числа. Теоретически, это потому, что если наименьшее число в максимальном наборе становится достаточно большим, ваше время проверки будет больше, потому что вам придется учитывать больше битов. Это создает впечатление, что оно будет немного выше, чем постоянное время. Тем не менее, вы также можете утверждать, что вероятность того, что следующее число находится в самом высоком наборе, приближается к нулю, когда n приближается к бесконечности, и поэтому вероятность того, что вам потребуется учесть больше битов, также приближается к 0, что будет аргументом для O (1) время.
Я не уверен, но моя интуиция говорит, что это O (log (log (n))) время. Это связано с тем, что вероятность увеличения наименьшего числа является логарифмической, а вероятность того, что число бит, которое необходимо учитывать для каждой проверки, также является логарифмической. Меня интересуют другие народы, потому что я не совсем уверен ...
источник
CheckTime + EnterTime
для каждого числа. Это имеет смысл только , если числа не ограничены, и такCheckTime
иEnterTime
будет как увеличиваться , по крайней мере , логарифмически в связи с увеличением размера цифр.это легко, если вы знаете Binary Heap Trees . Двоичные кучи поддерживают вставку в среднем постоянном времени, O (1). И дать вам легкий доступ к первым х элементов.
источник
Если из-за вопроса, который интервьюер действительно хотел задать, «можем ли мы убедиться, что каждый входящий номер обрабатывается в постоянное время», то, как уже указывалось многими (например, см. Ответ @ duedl0r), решение вашего друга уже O (1), и это было бы так, даже если бы он использовал несортированный список, или использовал сортировку по пузырькам, или что-то еще. В этом случае вопрос не имеет особого смысла, если только он не был сложным, или вы не помните его неправильно.
Я предполагаю, что вопрос интервьюера был осмысленным, что он не спрашивал, как сделать что-то, чтобы быть O (1), что, очевидно, уже так.
Поскольку сложность алгоритма опроса имеет смысл только тогда, когда размер входных данных растет бесконечно, и единственный вход, который может увеличиваться, это 100 - размер списка; Я предполагаю, что реальный вопрос заключался в том, «можем ли мы удостовериться, что мы получим Top N тратя O (1) раз на число (не O (N), как в решении вашего друга), возможно ли это?».
Первое, что приходит на ум, это подсчет сортировки, который купит сложность O (1) времени на число для задачи Top-N по цене использования пространства O (m), где m - длина диапазона входящих чисел. , Так что да, это возможно.
источник
Используйте очередь с минимальным приоритетом, реализованную с кучей Фибоначчи , которая имеет постоянное время вставки:
источник
O(log n)
амортизированном времени» , поэтому это все равно приведет к тому,O(log k)
гдеk
будет храниться количество элементов.Задача состоит в том, чтобы найти алгоритм O (1) длины N необходимого списка чисел. Так что не имеет значения, если вам нужны первые 100 или 10000 номеров, время вставки должно быть O (1).
Хитрость заключается в том, что хотя требование O (1) упомянуто для вставки списка, в вопросе ничего не сказано о порядке времени поиска во всем числовом пространстве, но оказывается, что это можно сделать O (1) также. Решение тогда следующее:
Организовать хеш-таблицу с номерами для ключей и парами связанных списков указателей для значений. Каждая пара указателей является началом и концом последовательности связанных списков. Обычно это будет просто один элемент, затем следующий. Каждый элемент в связанном списке идет рядом с элементом со следующим наибольшим номером. Таким образом, связанный список содержит отсортированную последовательность требуемых номеров. Сохраните запись с наименьшим номером.
Возьмите новое число x из случайного потока.
Это выше, чем последнее записанное наименьшее число? Да => Шаг 4, Нет => Шаг 2
Нажмите на хэш-таблицу с только что взятым номером. Есть ли запись? Да => Шаг 5. Нет => Возьмите новый номер x-1 и повторите этот шаг (это простой линейный поиск вниз, просто потерпите меня здесь, это можно улучшить, и я объясню как)
С элементом списка, только что полученным из хеш-таблицы, вставьте новый номер сразу после элемента в связанном списке (и обновите хеш)
Возьмите наименьшее записанное число l (и удалите его из хэша / списка).
Нажмите на хэш-таблицу с только что взятым номером. Есть ли запись? Да => Шаг 8. Нет => Возьмите новое число l + 1 и повторите этот шаг (это простой линейный поиск вверх)
При положительном попадании число становится новым самым низким числом. Перейти к шагу 2
Чтобы учесть дублирующиеся значения, хешу фактически необходимо поддерживать начало и конец последовательности связанного списка элементов, которые являются дубликатами. Таким образом, добавление или удаление элемента в данном ключе увеличивает или уменьшает указанный диапазон.
Вставка здесь - O (1). Упомянутые поиски, я думаю, что-то вроде, O (средняя разница между числами). Средняя разница увеличивается с размером пространства чисел, но уменьшается с требуемой длиной списка чисел.
Таким образом, стратегия линейного поиска довольно плохая, если числовое пространство велико (например, для 4-байтового типа int, от 0 до 2 ^ 32-1) и N = 100. Чтобы обойти эту проблему производительности, вы можете сохранить параллельные наборы хеш-таблиц, где числа округляются до более высоких величин (например, 1 с, 10 с, 100 с, 1000 с), чтобы получить подходящие ключи. Таким образом, вы можете увеличивать и уменьшать скорость, чтобы быстрее выполнять требуемые поиски. Я думаю, что производительность становится O (логарифмический диапазон), который является постоянным, то есть O (1) также.
Чтобы сделать это более понятным, представьте, что у вас есть номер 197 на руках. Вы попали в хэш-таблицу 10 с '190', она округляется до ближайшей десятки. Что-нибудь? Нет. Таким образом, вы понижаетесь в 10 с, пока не нажмете, скажем, 120. Затем вы можете начать с 129 в хэш-таблице 1 с, затем пробовать 128, 127, пока не нажмете что-нибудь. Теперь вы нашли, где в связанном списке вставить число 197. При его вставке необходимо также обновить хеш-таблицу 1 с записью 197, хеш-таблицу 10 с числом 190, 100 с 100 и т. Д. Большинство шагов Вы когда-либо должны сделать здесь в 10 раз журнал диапазона номеров.
Возможно, я ошибся в некоторых деталях, но так как это обмен программистами, а контекстом были интервью, я надеюсь, что вышеизложенное является достаточно убедительным ответом для такой ситуации.
РЕДАКТИРОВАТЬ Я добавил некоторые дополнительные детали, чтобы объяснить схему параллельной хеш-таблицы и то, как это означает, что упомянутые мной плохие линейные поиски могут быть заменены поиском O (1). Я также понял, что, конечно, нет необходимости искать следующий наименьший номер, потому что вы можете перейти прямо к нему, посмотрев в хеш-таблицу с наименьшим номером и перейдя к следующему элементу.
источник
Можем ли мы предположить, что числа имеют фиксированный тип данных, например, Integer? Если так, то ведите подсчет каждого добавленного числа. Это операция O (1).
Код VB.Net:
Когда вы возвращаете список, вы можете занять столько времени, сколько захотите. Просто перейдите от конца списка и создайте новый список из 100 самых высоких зарегистрированных значений. Это операция O (n), но это не имеет отношения к делу.
Изменить: На самом деле, это не имеет значения, если это фиксированный тип данных. Поскольку нет никаких ограничений на потребление памяти (или жесткого диска), вы можете сделать это для любого диапазона натуральных чисел.
источник
Сотни чисел легко сохраняются в массиве размером 100. Любое дерево, список или набор излишни, учитывая поставленную задачу.
Если входящий номер больше самого низкого (= последнего) в массиве, запустите все записи. Как только вы найдете первый номер, который меньше вашего нового номера (для этого вы можете использовать причудливый поиск), пропустите остаток массива, нажимая каждую запись «вниз» на единицу.
Поскольку вы сохраняете список отсортированным с самого начала, вам не нужно запускать какой-либо алгоритм сортировки вообще. Это O (1).
источник
Вы можете использовать бинарную Max-Heap. Вы должны будете отслеживать указатель на минимальный узел (который может быть неизвестным / нулевым).
Вы начинаете, вставляя первые 100 чисел в кучу. Макс будет на вершине. После этого вы всегда будете хранить там 100 номеров.
Затем, когда вы получите новый номер:
К сожалению,
findMinimumNode
это O (n), и вы несете эту стоимость один раз за вставку (но не во время вставки :). Удаление минимального узла и вставка нового узла, в среднем, O (1), потому что они будут стремиться к нижней части кучи.Если пойти по другому пути с бинарной мини-кучей, min находится сверху, что отлично подходит для нахождения минимума для сравнения, но отстой, когда нужно заменить минимум новым числом, которое> min. Это потому, что вы должны удалить минимальный узел (всегда O (logN)), а затем вставить новый узел (средний O (1)). Итак, у вас все еще есть O (logN), который лучше, чем Max-Heap, но не O (1).
Конечно, если N постоянно, то у вас всегда есть O (1). :)
источник