Mi è stato dato questo problema in un'intervista. Come avresti risposto?
Progettare una struttura dati che offra le seguenti operazioni nel tempo O (1):
- inserire
- rimuovere
- contiene
- ottieni un elemento casuale
Mi è stato dato questo problema in un'intervista. Come avresti risposto?
Progettare una struttura dati che offra le seguenti operazioni nel tempo O (1):
Risposte:
Si consideri una struttura dati composta da una tabella hash H e un array A. Le chiavi della tabella hash sono gli elementi nella struttura dati ei valori sono le loro posizioni nell'array.
poiché l'array deve aumentare automaticamente le dimensioni, verrà ammortizzato O (1) per aggiungere un elemento, ma immagino che sia OK.
La ricerca O (1) implica una struttura dati con hash .
A confronto:
hashtable.get((int)(Math.random()*hashtable.size()));
Potrebbe non piacerti, perché probabilmente stanno cercando una soluzione intelligente, ma a volte vale la pena attenersi alle tue pistole ... Una tabella hash soddisfa già i requisiti , probabilmente nel complesso meglio di qualsiasi altra cosa (anche se ovviamente in una costante ammortizzata tempo e con diversi compromessi rispetto ad altre soluzioni).
Il requisito difficile è la selezione di "elementi casuali": in una tabella hash, dovresti scansionare o sondare per un tale elemento.
Per l'hashing chiuso / indirizzamento aperto, la possibilità che un determinato bucket sia occupato è size() / capacity()
, ma in modo cruciale questo è tipicamente mantenuto in un intervallo moltiplicativo costante da un'implementazione della tabella hash (ad esempio la tabella può essere mantenuta più grande del suo contenuto corrente diciamo 1.2x a ~ 10x a seconda delle prestazioni / ottimizzazione della memoria). Ciò significa che in media possiamo aspettarci di cercare da 1,2 a 10 bucket, totalmente indipendente dalla dimensione totale del container; ammortizzato O (1).
Posso immaginare due semplici approcci (e moltissimi altri poco pratici):
cerca linearmente da un bucket casuale
prova ripetutamente i bucket casuali finché non ne trovi uno popolato
Non è un'ottima soluzione, ma può comunque essere un compromesso complessivo migliore rispetto ai costi generali di memoria e prestazioni derivanti dalla manutenzione di un secondo array di indici in ogni momento.
La soluzione migliore è probabilmente la tabella hash + array, è davvero veloce e deterministica.
Ma anche la risposta con il punteggio più basso (usa solo una tabella hash!) È davvero eccezionale!
Alla gente potrebbe non piacere a causa di "possibili cicli infiniti", e ho visto anche persone molto intelligenti avere questa reazione, ma è sbagliato! Eventi infinitamente improbabili semplicemente non accadono.
Supponendo il buon comportamento della tua fonte pseudo-casuale - che non è difficile da stabilire per questo particolare comportamento - e che le tabelle hash siano sempre piene almeno al 20%, è facile vedere che:
Non accadrà mai che getRandom () debba provare più di 1000 volte. Proprio mai . In effetti, la probabilità di un tale evento è 0,8 ^ 1000, che è 10 ^ -97, quindi dovremmo ripeterlo 10 ^ 88 volte per avere una possibilità su un miliardo che accada una volta. Anche se questo programma fosse in esecuzione a tempo pieno su tutti i computer dell'umanità fino alla morte del Sole, ciò non accadrà mai .
Per questa domanda userò due strutture dati
Passaggi: -
Codice :-
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;
}
}
}
}
- Complessità temporale O (1). - Complessità spaziale O (N).
Ecco una soluzione in C # a questo problema che mi è venuta in mente un po 'di tempo fa quando mi è stata posta la stessa domanda. Implementa Aggiungi, Rimuovi, Contiene e Random insieme ad altre interfacce .NET standard. Non che avresti mai bisogno di implementarlo in modo così dettagliato durante un colloquio, ma è bello avere una soluzione concreta da guardare ...
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
il messaggio "È già stato aggiunto un elemento con la stessa chiave". verrà lanciato (dall'indice Dictionary sottostante).
Possiamo usare l'hashing per supportare le operazioni in Θ (1) tempo.
insert (x) 1) Controlla se x è già presente eseguendo una ricerca sulla mappa hash. 2) Se non presente, inserirlo alla fine dell'array. 3) Aggiungi anche la tabella hash, x viene aggiunto come chiave e l'ultimo indice dell'array come indice.
remove (x) 1) Controlla se x è presente eseguendo una ricerca sulla mappa hash. 2) Se presente, trova il suo indice e rimuovilo dalla mappa hash. 3) Scambia l'ultimo elemento con questo elemento nell'array e rimuovi l'ultimo elemento. Lo scambio viene eseguito perché l'ultimo elemento può essere rimosso in tempo O (1). 4) Aggiorna l'indice dell'ultimo elemento nella mappa hash.
getRandom () 1) Genera un numero casuale da 0 all'ultimo indice. 2) Restituisce l'elemento della matrice all'indice generato casualmente.
search (x) Cerca x nella mappa hash.
Anche se questo è molto vecchio, ma poiché non c'è risposta in C ++, ecco i miei due centesimi.
#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<<" ";
}
}
};
Ecco un pezzo di codice client per testare la soluzione.
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;
}
In C # 3.0 + .NET Framework 4, un generico Dictionary<TKey,TValue>
è persino migliore di un Hashtable perché è possibile utilizzare il System.Linq
metodo di estensione ElementAt()
per indicizzare nell'array dinamico sottostante in cui KeyValuePair<TKey,TValue>
sono archiviati gli elementi:
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;
}
Tuttavia, per quanto ne so, un Hashtable (o la sua progenie del Dizionario) non è una vera soluzione a questo problema perché Put () può essere ammortizzato solo O (1), non vero O (1), perché è O (N ) al confine di ridimensionamento dinamico.
C'è una vera soluzione a questo problema? Tutto quello a cui riesco a pensare è che se specifichi una capacità iniziale di Dictionary / Hashtable un ordine di grandezza oltre quello che prevedi di aver mai bisogno, allora ottieni operazioni O (1) perché non hai mai bisogno di ridimensionare.
Sono d'accordo con Anon. Ad eccezione dell'ultimo requisito in cui è richiesto ottenere un elemento casuale con la stessa correttezza, tutti gli altri requisiti possono essere risolti solo utilizzando un singolo DS basato su hash. Sceglierò HashSet per questo in Java. Il modulo del codice hash di un elemento mi darà il numero di indice dell'array sottostante in tempo O (1). Posso usarlo per aggiungere, rimuovere e contenere operazioni.
Non possiamo farlo usando HashSet di Java? Per impostazione predefinita, fornisce insert, del, search all in O (1). Per getRandom possiamo usare l'iteratore di Set che comunque fornisce un comportamento casuale. Possiamo semplicemente iterare il primo elemento dell'insieme senza preoccuparci del resto degli elementi
public void getRandom(){
Iterator<integer> sitr = s.iterator();
Integer x = sitr.next();
return x;
}
/* 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`
}
}
Perché non usiamo epoch% arraysize per trovare un elemento casuale. Trovare la dimensione dell'array è O (n) ma la complessità ammortizzata sarà O (1).
Penso che possiamo usare doppiamente l'elenco dei collegamenti con la tabella hash. chiave sarà elemento e il suo valore associato sarà nodo in doppiamente linklist.