Мне рассказали об этой задаче в интервью. Как бы вы ответили?
Разработайте структуру данных, которая предлагает следующие операции за время O (1):
- вставить
- удалять
- содержит
- получить случайный элемент
data-structures
гильднер
источник
источник
Ответы:
Рассмотрим структуру данных, состоящую из хэш-таблицы H и массива A. Ключи хеш-таблицы - это элементы в структуре данных, а значения - их позиции в массиве.
поскольку массив должен автоматически увеличиваться в размере, добавление элемента будет амортизировано O (1), но я думаю, что это нормально.
источник
Поиск O (1) подразумевает хешированную структуру данных .
По сравнению:
источник
hashtable.get((int)(Math.random()*hashtable.size()));
Возможно, вам это не понравится, потому что они, вероятно, ищут умное решение, но иногда стоит придерживаться своего мнения ... Хеш-таблица уже удовлетворяет требованиям - вероятно, в целом лучше, чем что-либо еще (хотя, очевидно, в амортизированной константе время и с другими компромиссами по отношению к другим решениям).
Сложным требованием является выбор «случайного элемента»: в хеш-таблице вам нужно будет сканировать или исследовать такой элемент.
Для закрытого хеширования / открытой адресации вероятность того, что любое заданное ведро будет занято, есть
size() / capacity()
, но, что очень важно, это обычно поддерживается в постоянном мультипликативном диапазоне с помощью реализации хеш-таблицы (например, таблица может быть больше, чем ее текущее содержимое, скажем, в 1,2 раза). до ~ 10x в зависимости от производительности / настройки памяти). Это означает, что в среднем мы можем ожидать поиска от 1,2 до 10 сегментов - совершенно независимо от общего размера контейнера; амортизированная O (1).Я могу представить себе два простых подхода (и еще много неудобных):
линейный поиск из случайного сегмента
попробуйте несколько раз случайные корзины, пока не найдете заполненный
Не лучшее решение, но все же может быть лучшим общим компромиссом, чем накладные расходы на память и производительность, связанные с постоянным поддержанием второго массива индексов.
источник
Лучшее решение - это, наверное, хеш-таблица + массив, он очень быстрый и детерминированный.
Но ответ с самым низким рейтингом (просто используйте хеш-таблицу!) На самом деле тоже хорош!
Людям это может не понравиться из-за «возможных бесконечных циклов», и я видел, как у очень умных людей тоже была такая реакция, но это неправильно! Бесконечно маловероятные события просто не происходят.
Предполагая хорошее поведение вашего псевдослучайного источника - которое нетрудно установить для этого конкретного поведения - и что хеш-таблицы всегда заполнены как минимум на 20%, легко увидеть, что:
Он не будет никогда так случиться , что getRandom () должен попытаться более чем в 1000 раз. Просто никогда . В самом деле, вероятность такого события составляет 0,8 ^ 1000, что составляет 10 ^ -97, поэтому нам пришлось бы повторить это 10 ^ 88 раз, чтобы иметь один шанс из миллиарда, что это когда-либо произойдет. Даже если бы эта программа работала на всех компьютерах человечества на постоянной основе до самой смерти Солнца, этого никогда не произойдет.
источник
Для этого вопроса я буду использовать две структуры данных
Шаги: -
Код: -
- Временная сложность O (1). - Пространственная сложность O (N).
источник
Вот решение этой проблемы на C #, которое я придумал недавно, когда мне задали тот же вопрос. Он реализует функции «Добавить», «Удалить», «Содержит» и «Случайно» вместе с другими стандартными интерфейсами .NET. Не то чтобы вам когда-либо понадобилось реализовывать это так подробно во время интервью, но приятно иметь конкретное решение, на которое можно посмотреть ...
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; /// <summary> /// This class represents an unordered bag of items with the /// the capability to get a random item. All operations are O(1). /// </summary> /// <typeparam name="T">The type of the item.</typeparam> public class Bag<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable { private Dictionary<T, int> index; private List<T> items; private Random rand; private object syncRoot; /// <summary> /// Initializes a new instance of the <see cref="Bag<T>"/> class. /// </summary> public Bag() : this(0) { } /// <summary> /// Initializes a new instance of the <see cref="Bag<T>"/> class. /// </summary> /// <param name="capacity">The capacity.</param> public Bag(int capacity) { this.index = new Dictionary<T, int>(capacity); this.items = new List<T>(capacity); } /// <summary> /// Initializes a new instance of the <see cref="Bag<T>"/> class. /// </summary> /// <param name="collection">The collection.</param> public Bag(IEnumerable<T> collection) { this.items = new List<T>(collection); this.index = this.items .Select((value, index) => new { value, index }) .ToDictionary(pair => pair.value, pair => pair.index); } /// <summary> /// Get random item from bag. /// </summary> /// <returns>Random item from bag.</returns> /// <exception cref="System.InvalidOperationException"> /// The bag is empty. /// </exception> public T Random() { if (this.items.Count == 0) { throw new InvalidOperationException(); } if (this.rand == null) { this.rand = new Random(); } int randomIndex = this.rand.Next(0, this.items.Count); return this.items[randomIndex]; } /// <summary> /// Adds the specified item. /// </summary> /// <param name="item">The item.</param> public void Add(T item) { this.index.Add(item, this.items.Count); this.items.Add(item); } /// <summary> /// Removes the specified item. /// </summary> /// <param name="item">The item.</param> /// <returns></returns> public bool Remove(T item) { // Replace index of value to remove with last item in values list int keyIndex = this.index[item]; T lastItem = this.items[this.items.Count - 1]; this.items[keyIndex] = lastItem; // Update index in dictionary for last item that was just moved this.index[lastItem] = keyIndex; // Remove old value this.index.Remove(item); this.items.RemoveAt(this.items.Count - 1); return true; } /// <inheritdoc /> public bool Contains(T item) { return this.index.ContainsKey(item); } /// <inheritdoc /> public void Clear() { this.index.Clear(); this.items.Clear(); } /// <inheritdoc /> public int Count { get { return this.items.Count; } } /// <inheritdoc /> public void CopyTo(T[] array, int arrayIndex) { this.items.CopyTo(array, arrayIndex); } /// <inheritdoc /> public bool IsReadOnly { get { return false; } } /// <inheritdoc /> public IEnumerator<T> GetEnumerator() { foreach (var value in this.items) { yield return value; } } /// <inheritdoc /> IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// <inheritdoc /> public void CopyTo(Array array, int index) { this.CopyTo(array as T[], index); } /// <inheritdoc /> public bool IsSynchronized { get { return false; } } /// <inheritdoc /> public object SyncRoot { get { if (this.syncRoot == null) { Interlocked.CompareExchange<object>( ref this.syncRoot, new object(), null); } return this.syncRoot; } } }
источник
ArgumentException
сообщение «Элемент с таким же ключом уже добавлен». будет выброшено (из базового index Dictionary).Мы можем использовать хеширование для поддержки операций за время Θ (1).
insert (x) 1) Проверьте, присутствует ли x, выполнив поиск по хэш-карте. 2) Если нет, вставьте его в конец массива. 3) Добавьте также в хеш-таблицу, x добавляется как ключ, а последний индекс массива как индекс.
remove (x) 1) Проверьте, присутствует ли x, выполнив поиск по хэш-карте. 2) Если присутствует, найдите его индекс и удалите его из хеш-карты. 3) Поменяйте местами последний элемент с этим элементом в массиве и удалите последний элемент. Замена выполняется, потому что последний элемент может быть удален за O (1) раз. 4) Обновить индекс последнего элемента в хеш-карте.
getRandom () 1) Сгенерировать случайное число от 0 до последнего индекса. 2) Вернуть элемент массива по случайно сгенерированному индексу.
search (x) Выполните поиск x в хэш-карте.
источник
Хотя это и устарело, но поскольку на C ++ нет ответа, вот мои два цента.
Вот фрагмент клиентского кода для тестирования решения.
источник
В C # 3.0 + .NET Framework 4 универсальный
Dictionary<TKey,TValue>
вариант даже лучше, чем Hashtable, потому что вы можете использоватьSystem.Linq
метод расширенияElementAt()
для индексации в базовый динамический массив, в которомKeyValuePair<TKey,TValue>
хранятся элементы:Однако, насколько мне известно, Hashtable (или его потомок Dictionary) не является реальным решением этой проблемы, потому что Put () может амортизироваться только O (1), а не истинным O (1), потому что это O (N ) на границе динамического изменения размера.
Есть ли реальное решение этой проблемы? Все, о чем я могу думать, - это если вы укажете начальную емкость Dictionary / Hashtable на порядок больше того, что вы ожидаете когда-либо, тогда вы получите операции O (1), потому что вам никогда не нужно изменять размер.
источник
Я согласен с Аноном. За исключением последнего требования, когда требуется получение случайного элемента с равной справедливостью, все остальные требования могут быть выполнены только с использованием одного DS на основе хеша. Я выберу для этого HashSet в Java. Модуль хэш-кода элемента даст мне индекс no базового массива за время O (1). Я могу использовать это для операций добавления, удаления и добавления.
источник
Не можем ли мы сделать это с помощью HashSet of Java? По умолчанию он обеспечивает вставку, удаление, поиск всего в O (1). Для getRandom мы можем использовать итератор Set, который в любом случае дает случайное поведение. Мы можем просто перебирать первый элемент из набора, не беспокоясь об остальных элементах
источник
источник
Почему бы нам не использовать epoch% arrayysize для поиска случайного элемента. Определение размера массива составляет O (n), но амортизированная сложность будет O (1).
источник
Я думаю, что мы можем использовать список двойных ссылок с хеш-таблицей. ключ будет элементом, а связанное с ним значение будет узлом в двойном списке ссылок.
источник