我在接受采访时得到了这个问题。你怎么回答?
设计一个在O(1)时间内提供以下操作的数据结构:
答案 0 :(得分:130)
考虑由哈希表H和数组A组成的数据结构。哈希表键是数据结构中的元素,值是它们在数组中的位置。
由于数组需要自动增加大小,所以要分摊O(1)来添加元素,但我想这没关系。
答案 1 :(得分:20)
O(1)查找意味着hashed data structure。
相比之下:
答案 2 :(得分:5)
你可能不喜欢这样,因为他们可能正在寻找一个聪明的解决方案,但有时候坚持你的枪是值得的......一个哈希表已经满足了要求 - 总体上可能更好比其他任何东西都要好(尽管显然是在摊销的固定时间内,并且对其他解决方案有不同的妥协)。
棘手的要求是“随机元素”选择:在哈希表中,您需要扫描或探测这样的元素。
对于封闭散列/开放寻址,任何给定存储桶被占用的可能性为size() / capacity()
,但关键是这通常通过散列表实现保持在一个恒定的乘法范围内(例如,表可以保持更大比其当前内容为1.2x至~10x,具体取决于性能/内存调整)。这意味着平均而言我们可以期望搜索1.2到10个桶 - 完全独立于容器的总大小;摊销O(1)。
我可以想象两种简单的方法(还有很多很简单的方法):
从随机存储桶中线性搜索
重复尝试随机存储桶,直到找到已填充的存储桶
不是一个很好的解决方案,但可能仍然是比始终维护第二个索引数组的内存和性能开销更好的整体折衷。
答案 3 :(得分:3)
最好的解决方案可能是哈希表+数组,它真实快速且具有确定性。
但是评分最低的答案(只是使用哈希表!)实际上也很棒!
人们可能不喜欢这个,因为“可能无限循环”,而且我看到非常聪明的人也有这种反应,但这是错误的! 不会发生的事件绝对不可能发生。
假设伪随机源的良好行为 - 这对于这种特定行为并不难建立 - 并且哈希表总是至少满20%,很容易看出:
getRandom()必须尝试超过1000次永远。只是从不。事实上,这样一个事件的概率是0.8 ^ 1000,即10 ^ -97-所以我们必须重复它10 ^ 88次才有一次机会在十亿次事件中发生过一次。即使这个程序在人类的所有计算机上全职运行,直到太阳死亡,这将永远发生。
答案 4 :(得分:3)
对于这个问题,我将使用两个数据结构
步骤: -
代码: -
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<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;
}
}
}
答案 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将是元素,其关联值将是双重链接列表中的节点。