Data structure: insert, remove, contains, get random element, all at O(1)

后端 未结 14 593
别跟我提以往
别跟我提以往 2020-11-29 14:53

I was given this problem in an interview. How would you have answered?

Design a data structure that offers the following operations in O(1) time:

  • inse
相关标签:
14条回答
  • 2020-11-29 15:05

    Here is a C# solution to that problem I came up with a little while back when asked the same question. It implements Add, Remove, Contains, and Random along with other standard .NET interfaces. Not that you would ever need to implement it in such detail during an interview but it's nice to have a concrete solution to look at...

    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;
    
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 15:05

    I think we can use doubly link list with hash table. key will be element and its associated value will be node in doubly linklist.

    1. insert(H,E) : insert node in doubly linklist and make entry as H[E]=node; O(1)
    2. delete(H,E) : get node address by H(E), goto previous of this node and delete and make H(E) as NULL, so O(1)
    3. contains(H,E) and getRandom(H) are obviuosly O(1)
    0 讨论(0)
  • 2020-11-29 15:11
    /* 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`
        }
    }
    
    0 讨论(0)
  • 2020-11-29 15:12

    You might not like this, because they're probably looking for a clever solution, but sometimes it pays to stick to your guns... A hash table already satisfies the requirements - probably better overall than anything else will (albeit obviously in amortised constant time, and with different compromises to other solutions).

    The requirement that's tricky is the "random element" selection: in a hash table, you would need to scan or probe for such an element.

    For closed hashing / open addressing, the chance of any given bucket being occupied is size() / capacity(), but crucially this is typically kept in a constant multiplicative range by a hash-table implementation (e.g. the table may be kept larger than its current contents by say 1.2x to ~10x depending on performance/memory tuning). This means on average we can expect to search 1.2 to 10 buckets - totally independent of the total size of the container; amortised O(1).

    I can imagine two simple approaches (and a great many more fiddly ones):

    • search linearly from a random bucket

      • consider empty/value-holding buckets ala "--AC-----B--D": you can say that the first "random" selection is fair even though it favours B, because B had no more probability of being favoured than the other elements, but if you're doing repeated "random" selections using the same values then clearly having B repeatedly favoured may be undesirable (nothing in the question demands even probabilities though)
    • try random buckets repeatedly until you find a populated one

      • "only" capacity() / size() average buckets visited (as above) - but in practical terms more expensive because random number generation is relatively expensive, and infinitely bad if infinitely improbable worst-case behaviour...
        • a faster compromise would be to use a list of pre-generated random offsets from the initial randomly selected bucket, %-ing them into the bucket count

    Not a great solution, but may still be a better overall compromise than the memory and performance overheads of maintaining a second index array at all times.

    0 讨论(0)
  • 2020-11-29 15:14

    We can use hashing to support operations in Θ(1) time.

    insert(x) 1) Check if x is already present by doing a hash map lookup. 2) If not present, then insert it at the end of the array. 3) Add in hash table also, x is added as key and last array index as index.

    remove(x) 1) Check if x is present by doing a hash map lookup. 2) If present, then find its index and remove it from hash map. 3) Swap the last element with this element in array and remove the last element. Swapping is done because the last element can be removed in O(1) time. 4) Update index of last element in hash map.

    getRandom() 1) Generate a random number from 0 to last index. 2) Return the array element at the randomly generated index.

    search(x) Do a lookup for x in hash map.

    0 讨论(0)
  • 2020-11-29 15:14

    In C# 3.0 + .NET Framework 4, a generic Dictionary<TKey,TValue> is even better than a Hashtable because you can use the System.Linq extension method ElementAt() to index into the underlying dynamic array where the KeyValuePair<TKey,TValue> elements are stored :

    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;
    }
    

    However, as far as I know, a Hashtable (or its Dictionary progeny) is not a real solution to this problem because Put() can only be amortized O(1) , not true O(1) , because it is O(N) at the dynamic resize boundary.

    Is there a real solution to this problem ? All I can think of is if you specify a Dictionary/Hashtable initial capacity an order of magnitude beyond what you anticipate ever needing, then you get O(1) operations because you never need to resize.

    0 讨论(0)
提交回复
热议问题