数据结构:插入,删除,包含,获取随机元素,全部在O(1)

时间:2011-04-15 20:48:46

标签: data-structures

我在接受采访时得到了这个问题。你怎么回答?

设计一个在O(1)时间内提供以下操作的数据结构:

  • 插入
  • 除去
  • 包含
  • 获取随机元素

14 个答案:

答案 0 :(得分:130)

考虑由哈希表H和数组A组成的数据结构。哈希表键是数据结构中的元素,值是它们在数组中的位置。

  1. insert(value):将值附加到数组,让我成为A中的索引。设置H [值] = i。
  2. remove(value):我们要用A中的最后一个元素替换A中包含值的单元格。让d成为索引m处数组A中的最后一个元素。让我成为H [value],即要删除的值的数组中的索引。设置A [i] = d,H [d] = i,将数组的大小减1,并从H中删除值。
  3. contains(value):return H.contains(value)
  4. getRandomElement():令r = random(当前大小为A)。返回A [r]。
  5. 由于数组需要自动增加大小,所以要分摊O(1)来添加元素,但我想这没关系。

答案 1 :(得分:20)

O(1)查找意味着hashed data structure

相比之下:

  • O(1)使用O(N)查找插入/删除意味着链接列表。
  • O(1)insert,O(N)delete和O(N)lookup意味着数组支持列表
  • O(logN)insert / delete / lookup意味着树或堆。

答案 2 :(得分:5)

你可能不喜欢这样,因为他们可能正在寻找一个聪明的解决方案,但有时候坚持你的枪是值得的......一个哈希表已经满足了要求 - 总体上可能更好比其他任何东西都要好(尽管显然是在摊销的固定时间内,并且对其他解决方案有不同的妥协)。

棘手的要求是“随机元素”选择:在哈希表中,您需要扫描或探测这样的元素。

对于封闭散列/开放寻址,任何给定存储桶被占用的可能性为size() / capacity(),但关键是这通常通过散列表实现保持在一个恒定的乘法范围内(例如,表可以保持更大比其当前内容为1.2x至~10x,具体取决于性能/内存调整)。这意味着平均而言我们可以期望搜索1.2到10个桶 - 完全独立于容器的总大小;摊销O(1)。

我可以想象两种简单的方法(还有很多很简单的方法):

  • 从随机存储桶中线性搜索

    • 考虑空/值保持桶ala“--AC ----- B - D”:你可以说第一个“随机”选择是公平的,即使它有利于B ,因为B没有比其他元素更有可能受到青睐,但是如果你使用相同的值进行重复的“随机”选择,那么明显地重复使用B可能是不可取的(尽管问题中没有任何内容需要甚至概率)< / LI>
  • 重复尝试随机存储桶,直到找到已填充的存储桶

    • “only”capacity()/ size()平均访问过的桶(如上所述) - 但实际上更昂贵,因为随机数生成相对昂贵,如果无限不可能的最差,则无限糟糕 - 案件行为......
      • 更快的折衷方案是使用初始随机选择的存储桶中预先生成的随机偏移列表,% - 将它们计入存储桶计数

不是一个很好的解决方案,但可能仍然是比始终维护第二个索引数组的内存和性能开销更好的整体折衷。

答案 3 :(得分:3)

最好的解决方案可能是哈希表+数组,它真实快速且具有确定性。

但是评分最低的答案(只是使用哈希表!)实际上也很棒!

  • 带有重新哈希的哈希表,或新的桶选择(即每个桶一个元素,没有链接列表)
  • getRandom()反复尝试选择一个随机桶,直到它为空。
  • 作为故障保护,可能是getRandom(),在N(元素数量)尝试失败后,在[0,N-1]中选择一个随机索引i然后线性地遍历哈希表并选择#i -th element。

人们可能不喜欢这个,因为“可能无限循环”,而且我看到非常聪明的人也有这种反应,但这是错误的! 不会发生的事件绝对不可能发生。

假设伪随机源的良好行为 - 这对于这种特定行为并不难建立 - 并且哈希表总是至少满20%,很容易看出:

getRandom()必须尝试超过1000次永远。只是从不。事实上,这样一个事件的概率是0.8 ^ 1000,即10 ^ -97-所以我们必须重复它10 ^ 88次才有一次机会在十亿次事件中发生过一次。即使这个程序在人类的所有计算机上全职运行,直到太阳死亡,这将永远发生。

答案 4 :(得分:3)

对于这个问题,我将使用两个数据结构

  • HashMap
  • ArrayList / Array / Double LinkedList。

步骤: -

  1. 插入: - 检查HashMap中是否已存在X - 时间复杂度为O(1)。如果不存在则添加到ArrayList的末尾 - 时间复杂度O(1)。 将它添加到HashMap中,x也作为键,最后一个索引作为值 - 时间复杂度O(1)。
  2. 删除: - 检查HashMap中是否存在X - 时间复杂度为O(1)。如果存在,则找到其索引并将其从HashMap - 时间复杂度O(1)中删除。将此元素与ArrayList中的最后一个元素交换,并删除最后一个元素 - 时间复杂度O(1)。更新HashMap中最后一个元素的索引 - 时间复杂度O(1)。
  3. GetRandom: - 生成从Array到0的最后一个索引的随机数。在生成的随机索引处返回ArrayList元素 - 时间复杂度O(1)。
  4. 搜索: - 在HashMap中查看x作为键。 - 时间复杂度O(1)。
  5. 代码: -

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Random;
    import java.util.Scanner;
    
    
    public class JavaApplication1 {
    
        public static void main(String args[]){
           Scanner sc = new Scanner(System.in);
            ArrayList<Integer> al =new ArrayList<Integer>();
            HashMap<Integer,Integer> mp = new HashMap<Integer,Integer>();  
            while(true){
                System.out.println("**menu**");
                System.out.println("1.insert");
                System.out.println("2.remove");
                System.out.println("3.search");
                System.out.println("4.rendom");
                int ch = sc.nextInt();
                switch(ch){
                    case 1 : System.out.println("Enter the Element ");
                            int a = sc.nextInt();
                            if(mp.containsKey(a)){
                                System.out.println("Element is already present ");
                            }
                            else{
                                al.add(a);
                                mp.put(a, al.size()-1);
    
                            }
                            break;
                    case 2 : System.out.println("Enter the Element Which u want to remove");
                            a = sc.nextInt();
                            if(mp.containsKey(a)){
    
                                int size = al.size();
                                int index = mp.get(a);
    
                                int last = al.get(size-1);
                                Collections.swap(al, index,  size-1);
    
                                al.remove(size-1);
                                mp.put(last, index);
    
                                System.out.println("Data Deleted");
    
                            }
                            else{
                                System.out.println("Data Not found");
                            }
                            break;
                    case 3 : System.out.println("Enter the Element to Search");
                            a = sc.nextInt();
                            if(mp.containsKey(a)){
                                System.out.println(mp.get(a));
                            }
                            else{
                                System.out.println("Data Not Found");
                            }
                            break;
                    case 4 : Random rm = new Random();
                            int index = rm.nextInt(al.size());
                            System.out.println(al.get(index));
                            break;
    
                }
            }
        }
    
    }
    

    - 时间复杂度O(1)。 - 空间复杂度O(N)。

答案 5 :(得分:1)

这是一个针对该问题的C#解决方案,当我被问到同样的问题时,我想出了一会儿。它实现了Add,Remove,Contains和Random以及其他标准.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&lt;T&gt;"/> class.
    /// </summary>
    public Bag()
        : this(0)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Bag&lt;T&gt;"/> 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&lt;T&gt;"/> 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;

        }
    }
}

答案 6 :(得分:1)

虽然这已经过时了,但由于C ++中没有答案,所以这是我的两分钱。

#include <vector>
#include <unordered_map>
#include <stdlib.h>

template <typename T> class bucket{
    int size;
    std::vector<T> v;
    std::unordered_map<T, int> m;
public:
    bucket(){
        size = 0;
        std::vector<T>* v = new std::vector<T>();
        std::unordered_map<T, int>* m = new std::unordered_map<T, int>();
    }
    void insert(const T& item){
        //prevent insertion of duplicates
        if(m.find(item) != m.end()){
            exit(-1);
        }
        v.push_back(item);
        m.emplace(item, size);
        size++;

    }
    void remove(const T& item){
        //exits if the item is not present in the list
        if(m[item] == -1){
            exit(-1);
        }else if(m.find(item) == m.end()){
            exit(-1);
        }

        int idx = m[item];
        m[v.back()] = idx;
        T itm = v[idx];
        v.insert(v.begin()+idx, v.back());
        v.erase(v.begin()+idx+1);
        v.insert(v.begin()+size, itm);
        v.erase(v.begin()+size);
        m[item] = -1;
        v.pop_back();
        size--;

    }

     T& getRandom(){
      int idx = rand()%size;
      return v[idx];

     }

     bool lookup(const T& item){
       if(m.find(item) == m.end()) return false;
       return true;

     }
    //method to check that remove has worked
    void print(){
        for(auto it = v.begin(); it != v.end(); it++){
            std::cout<<*it<<" ";
        }
    }
};

这是一段测试解决方案的客户端代码。

int main() {

    bucket<char>* b = new bucket<char>();
    b->insert('d');
    b->insert('k');
    b->insert('l');
    b->insert('h');
    b->insert('j');
    b->insert('z');
    b->insert('p');

    std::cout<<b->random()<<std::endl;
    b->print();
    std::cout<<std::endl;
    b->remove('h');
    b->print();

    return 0;
}

答案 7 :(得分:0)

在C#3.0 + .NET Framework 4中,通用Dictionary<TKey,TValue>甚至比Hashtable更好,因为您可以使用System.Linq扩展方法ElementAt()来索引基础动态数组,其中存储了KeyValuePair<TKey,TValue>个元素:

using System.Linq;

Random _generator = new Random((int)DateTime.Now.Ticks);

Dictionary<string,object> _elements = new Dictionary<string,object>();

....

Public object GetRandom()
{
     return _elements.ElementAt(_generator.Next(_elements.Count)).Value;
}

但是,据我所知,Hashtable(或其词典后代)不是这个问题的真正解决方案,因为Put()只能分摊O(1),而不是真正的O(1),因为它是O(N)处于动态调整大小边界。

这个问题有真正的解决方案吗?我能想到的是,如果你将Dictionary / Hashtable的初始容量指定为超出预期所需的数量级,那么你就可以获得O(1)操作,因为你永远不需要调整大小。

答案 8 :(得分:0)

我们可以使用散列来支持Θ(1)时间内的操作。

插入(X) 1)通过执行哈希映射查找来检查x是否已存在。 2)如果不存在,则将其插入阵列的末尾。 3)也添加哈希表,x作为键添加,最后一个数组索引作为索引。

删除(x)的 1)通过执行哈希映射查找来检查x是否存在。 2)如果存在,则找到其索引并将其从哈希映射中删除。 3)在数组中使用此元素交换最后一个元素并删除最后一个元素。 完成交换是因为可以在O(1)时间内删除最后一个元素。 4)更新哈希映射中最后一个元素的索引。

<强> getRandom() 1)生成从0到最后索引的随机数。 2)以随机生成的索引返回数组元素。

搜索(x)的 在哈希映射中查找x。

答案 9 :(得分:0)

我同意Anon。除了要求获得具有相同公平性的随机元素的最后一个要求之外,所有其他要求只能使用单个基于哈希的DS来解决。我将在Java中为此选择HashSet。元素的哈希码的模数将在O(1)时间内给出基础数组的索引号。我可以使用它来添加,删除和包含操作。

答案 10 :(得分:0)

我们不能使用HashSet of Java吗?默认情况下,它在O(1)中提供insert,del,search。 对于getRandom,我们可以使用Set的迭代器,它总是提供随机行为。我们可以从集合中迭代第一个元素,而不必担心其余的元素

public void getRandom(){
    Iterator<integer> sitr = s.iterator();
    Integer x = sitr.next();    
    return x;
}

答案 11 :(得分:0)

/* Java program to design a data structure that support folloiwng operations
   in Theta(n) time
   a) Insert
   b) Delete
   c) Search
   d) getRandom */
import java.util.*;

// class to represent the required data structure
class MyDS
{
   ArrayList<Integer> arr;   // A resizable array

   // A hash where keys are array elements and vlaues are
   // indexes in arr[]
   HashMap<Integer, Integer>  hash;

   // Constructor (creates arr[] and hash)
   public MyDS()
   {
       arr = new ArrayList<Integer>();
       hash = new HashMap<Integer, Integer>();
   }

   // A Theta(1) function to add an element to MyDS
   // data structure
   void add(int x)
   {
      // If ekement is already present, then noting to do
      if (hash.get(x) != null)
          return;

      // Else put element at the end of arr[]
      int s = arr.size();
      arr.add(x);

      // And put in hash also
      hash.put(x, s);
   }

   // A Theta(1) function to remove an element from MyDS
   // data structure
   void remove(int x)
   {
       // Check if element is present
       Integer index = hash.get(x);
       if (index == null)
          return;

       // If present, then remove element from hash
       hash.remove(x);

       // Swap element with last element so that remove from
       // arr[] can be done in O(1) time
       int size = arr.size();
       Integer last = arr.get(size-1);
       Collections.swap(arr, index,  size-1);

       // Remove last element (This is O(1))
       arr.remove(size-1);

       // Update hash table for new index of last element
       hash.put(last, index);
    }

    // Returns a random element from MyDS
    int getRandom()
    {
       // Find a random index from 0 to size - 1
       Random rand = new Random();  // Choose a different seed
       int index = rand.nextInt(arr.size());

       // Return element at randomly picked index
       return arr.get(index);
    }

    // Returns index of element if element is present, otherwise null
    Integer search(int x)
    {
       return hash.get(x);
    }
}

// Driver class
class Main
{
    public static void main (String[] args)
    {
        MyDS ds = new MyDS();
        ds.add(10);
        ds.add(20);
        ds.add(30);
        ds.add(40);
        System.out.println(ds.search(30));
        ds.remove(20);
        ds.add(50);
        System.out.println(ds.search(50));
        System.out.println(ds.getRandom());`enter code here`
    }
}

答案 12 :(得分:-2)

我们为什么不使用epoch%arraysize查找随机元素。查找数组大小为O(n),但摊余复杂度为O(1)。

答案 13 :(得分:-3)

我认为我们可以使用带有哈希表的双重链接列表。 key将是元素,其关联值将是双重链接列表中的节点。

  1. insert(H,E):在双向链表中插入节点,并以H [E] =节点进行输入; O(1)
  2. 删除(H,E):通过H(E)获取节点地址,转到此节点的前一个并删除并使H(E)为NULL,因此O(1)
  3. 包含(H,E)和getRandom(H)是显而易见的O(1)