Me dieron este problema en una entrevista. ¿Cómo habrías respondido?
Diseñe una estructura de datos que ofrezca las siguientes operaciones en O (1) tiempo:
- insertar
- eliminar
- contiene
- obtener elemento aleatorio
Me dieron este problema en una entrevista. ¿Cómo habrías respondido?
Diseñe una estructura de datos que ofrezca las siguientes operaciones en O (1) tiempo:
Respuestas:
Considere una estructura de datos compuesta por una tabla hash H y una matriz A. Las claves de la tabla hash son los elementos de la estructura de datos y los valores son sus posiciones en la matriz.
dado que la matriz necesita aumentar automáticamente de tamaño, se amortizará O (1) para agregar un elemento, pero supongo que está bien.
La búsqueda de O (1) implica una estructura de datos hash .
En comparación:
hashtable.get((int)(Math.random()*hashtable.size()));
Puede que esto no le guste, porque probablemente estén buscando una solución inteligente, pero a veces vale la pena ceñirse a sus armas ... Una tabla hash ya satisface los requisitos , probablemente mejor en general que cualquier otra cosa (aunque obviamente en constante amortizada tiempo, y con compromisos diferentes a otras soluciones).
El requisito que es complicado es la selección del "elemento aleatorio": en una tabla hash, necesitaría escanear o sondear dicho elemento.
Para el hash cerrado / direccionamiento abierto, la posibilidad de que se ocupe un cubo determinado es size() / capacity()
, pero lo más importante es que esto se mantiene en un rango multiplicativo constante mediante una implementación de tabla hash (por ejemplo, la tabla puede mantenerse más grande que su contenido actual, por ejemplo, 1.2x a ~ 10x dependiendo del rendimiento / ajuste de la memoria). Esto significa que, en promedio, podemos esperar buscar entre 1,2 y 10 cubos, totalmente independiente del tamaño total del contenedor; amortizado O (1).
Puedo imaginar dos enfoques simples (y muchos más complicados):
buscar linealmente desde un depósito aleatorio
intente cubos aleatorios repetidamente hasta que encuentre uno poblado
No es una gran solución, pero aún puede ser un mejor compromiso general que los gastos generales de memoria y rendimiento de mantener una segunda matriz de índices en todo momento.
La mejor solución es probablemente la tabla hash + matriz, es realmente rápida y determinista.
Pero la respuesta con la calificación más baja (¡solo use una tabla hash!) ¡También es excelente!
Puede que a la gente no le guste esto debido a "posibles bucles infinitos", y he visto a personas muy inteligentes que también tienen esta reacción, ¡pero está mal! Los eventos infinitamente improbables simplemente no suceden.
Suponiendo el buen comportamiento de su fuente pseudoaleatoria, que no es difícil de establecer para este comportamiento en particular, y que las tablas hash siempre están llenas al menos en un 20%, es fácil ver que:
Será Nunca suceder que getRandom () tiene que probar más de 1000 veces. Simplemente nunca . De hecho, la probabilidad de que se produzca un evento de este tipo es 0,8 ^ 1000, que es 10 ^ -97, por lo que tendríamos que repetirlo 10 ^ 88 veces para tener una posibilidad entre mil millones de que ocurra una vez. Incluso si este programa se ejecutara a tiempo completo en todas las computadoras de la humanidad hasta que el Sol muera, esto nunca sucederá.
Para esta pregunta utilizaré dos estructuras de datos
Pasos: -
Código: -
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;
}
}
}
}
- Complejidad temporal O (1). - Complejidad espacial O (N).
Aquí hay una solución de C # para ese problema que se me ocurrió hace un tiempo cuando me hicieron la misma pregunta. Implementa Agregar, Eliminar, Contiene y Aleatorio junto con otras interfaces .NET estándar. No es que necesite implementarlo con tanto detalle durante una entrevista, pero es bueno tener una solución concreta para analizar ...
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
con el mensaje "Ya se agregó un elemento con la misma clave". se lanzará (desde el diccionario de índice subyacente).
Podemos usar hash para respaldar operaciones en Θ (1) tiempo.
insert (x) 1) Verifique si x ya está presente haciendo una búsqueda de mapa hash. 2) Si no está presente, insértelo al final de la matriz. 3) Agregue también la tabla hash, x se agrega como clave y el último índice de matriz como índice.
remove (x) 1) Verifique si x está presente haciendo una búsqueda de mapa hash. 2) Si está presente, busque su índice y elimínelo del mapa hash. 3) Cambie el último elemento con este elemento en la matriz y elimine el último elemento. El intercambio se realiza porque el último elemento se puede eliminar en O (1) tiempo. 4) Actualizar el índice del último elemento en el mapa hash.
getRandom () 1) Genera un número aleatorio desde 0 hasta el último índice. 2) Devuelve el elemento de la matriz en el índice generado aleatoriamente.
search (x) Realiza una búsqueda de x en el mapa hash.
Aunque esto es muy antiguo, pero como no hay respuesta en C ++, aquí está mi granito de arena.
#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<<" ";
}
}
};
Aquí hay un fragmento de código de cliente para probar la solución.
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;
}
En C # 3.0 + .NET Framework 4, un genérico Dictionary<TKey,TValue>
es incluso mejor que un Hashtable porque puede usar el System.Linq
método de extensión ElementAt()
para indexar en la matriz dinámica subyacente donde KeyValuePair<TKey,TValue>
se almacenan los elementos:
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;
}
Sin embargo, hasta donde yo sé, una tabla hash (o su progenie del diccionario) no es una solución real a este problema porque Put () solo se puede amortizar O (1), no O (1) verdadero, porque es O (N ) en el límite de cambio de tamaño dinámico.
¿Existe una solución real a este problema? Todo lo que puedo pensar es que si especifica una capacidad inicial de Dictionary / Hashtable en un orden de magnitud más allá de lo que anticipa que necesitará, entonces obtendrá operaciones O (1) porque nunca necesita cambiar el tamaño.
Estoy de acuerdo con Anon. Excepto por el último requisito, donde se requiere obtener un elemento aleatorio con la misma imparcialidad, todos los demás requisitos se pueden abordar solo utilizando un único DS basado en Hash. Elegiré HashSet para esto en Java. El módulo de código hash de un elemento me dará el número de índice de la matriz subyacente en el tiempo O (1). Puedo usar eso para agregar, eliminar y contiene operaciones.
¿No podemos hacer esto usando HashSet of Java? Proporciona insertar, eliminar, buscar todo en O (1) de forma predeterminada. Para getRandom podemos hacer uso del iterador de Set que de todos modos da un comportamiento aleatorio. Podemos simplemente iterar el primer elemento del conjunto sin preocuparnos por el resto de los elementos
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`
}
}
¿Por qué no usamos epoch% arraysize para encontrar un elemento aleatorio? Encontrar el tamaño de la matriz es O (n) pero la complejidad amortizada será O (1).
Creo que podemos usar doble lista de enlaces con tabla hash. la clave será elemento y su valor asociado será nodo en doble lista de enlaces.