인터뷰에서이 문제를 받았습니다. 어떻게 대답하겠습니까?
O (1) 시간에 다음 작업을 제공하는 데이터 구조를 설계합니다.
- 끼워 넣다
- 없애다
- 포함
- 임의의 요소를 얻다
인터뷰에서이 문제를 받았습니다. 어떻게 대답하겠습니까?
O (1) 시간에 다음 작업을 제공하는 데이터 구조를 설계합니다.
답변:
해시 테이블 H와 배열 A로 구성된 데이터 구조를 고려하십시오. 해시 테이블 키는 데이터 구조의 요소이고 값은 배열에서의 위치입니다.
배열의 크기가 자동으로 증가해야하기 때문에 요소를 추가하기 위해 O (1)을 분할 할 것입니다.하지만 괜찮다고 생각합니다.
O (1) 조회는 해시 된 데이터 구조를 의미합니다 .
비교 :
hashtable.get((int)(Math.random()*hashtable.size()));
아마도 현명한 솔루션을 찾고 있기 때문에 이것을 좋아하지 않을 수도 있지만 때로는 총을 고수하는 것이 좋습니다 ... 해시 테이블은 이미 요구 사항을 충족합니다 -아마도 다른 어떤 것보다 전반적으로 더 좋습니다 (분명히 상각 상수 시간 및 다른 솔루션에 대한 다른 타협).
까다로운 요구 사항은 "무작위 요소"선택입니다. 해시 테이블에서 이러한 요소를 스캔하거나 조사해야합니다.
폐쇄 형 해싱 / 개방형 주소 지정의 경우 주어진 버킷이 점유 될 가능성은 size() / capacity()
이지만 결정적으로 이것은 일반적으로 해시 테이블 구현에 의해 일정한 곱셈 범위로 유지됩니다 (예 : 테이블이 현재 내용보다 예를 들어 1.2 배 더 크게 유지 될 수 있음). 성능 / 메모리 조정에 따라 ~ 10x). 이는 평균적으로 컨테이너의 전체 크기와 완전히 독립적 인 1.2 ~ 10 개의 버킷을 검색 할 수 있음을 의미합니다. 상각 된 O (1).
나는 두 가지 간단한 접근 방식을 상상할 수 있습니다 (그리고 훨씬 더 어리석은 접근 방식).
임의 버킷에서 선형 검색
채워진 버킷을 찾을 때까지 무작위 버킷을 반복적으로 시도하십시오.
훌륭한 솔루션은 아니지만 항상 두 번째 인덱스 배열을 유지 관리하는 메모리 및 성능 오버 헤드보다 전반적으로 더 나은 타협점 일 수 있습니다.
가장 좋은 해결책은 아마도 해시 테이블 + 배열 일 것입니다. 정말 빠르고 결정적입니다.
그러나 가장 낮은 등급의 답변 (해시 테이블을 사용하십시오!)도 실제로 훌륭합니다!
사람들은 "가능한 무한 루프"때문에 이것을 좋아하지 않을 수 있습니다. 그리고 저는 매우 똑똑한 사람들도 이런 반응을 보이는 것을 보았지만 그것은 틀 렸습니다! 무한히 가능성이없는 이벤트 는 발생 하지 않습니다 .
이 특정 동작에 대해 설정하기 어렵지 않은 의사 랜덤 소스의 좋은 동작과 해시 테이블이 항상 최소 20 % 가득 차 있다고 가정하면 다음을 쉽게 알 수 있습니다.
getRandom ()이 1000 번 이상 시도 하지 않아도 됩니다 . 그냥 결코 . 실제로 그러한 사건의 확률은 0.8 ^ 1000, 즉 10 ^ -97입니다. 그래서 우리는 그것을 10 ^ 88 번 반복해야 10 억분의 1의 기회가 한 번 발생합니다. 이 프로그램이 태양이 죽을 때까지 인류의 모든 컴퓨터에서 풀 타임으로 실행 되더라도 결코 일어나지 않을 것입니다.
이 질문에는 두 개의 데이터 구조를 사용합니다.
단계 :-
코드 :-
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).
다음은 같은 질문을 받았을 때 잠시 생각해 낸 문제에 대한 C # 솔루션입니다. 다른 표준 .NET 인터페이스와 함께 Add, Remove, Contains 및 Random을 구현합니다. 인터뷰 중에 그렇게 자세하게 구현해야한다는 것은 아니지만 구체적인 해결책을 찾는 것이 좋습니다.
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
"같은 키를 가진 항목이 이미 추가되었습니다."라는 메시지가 표시됩니다. (기본 인덱스 사전에서) throw됩니다.
해싱을 사용하여 Θ (1) 시간에 작업을 지원할 수 있습니다.
insert (x) 1) 해시 맵 조회를 수행하여 x가 이미 있는지 확인합니다. 2) 없으면 배열 끝에 삽입합니다. 3) 해시 테이블도 추가하면 x는 키로 추가되고 마지막 배열 인덱스는 인덱스로 추가됩니다.
remove (x) 1) 해시 맵 조회를 수행하여 x가 있는지 확인합니다. 2) 존재하는 경우 색인을 찾아 해시 맵에서 제거하십시오. 3) 마지막 요소를 배열의이 요소로 바꾸고 마지막 요소를 제거합니다. 마지막 요소는 O (1) 시간에 제거 할 수 있기 때문에 스와핑이 수행됩니다. 4) 해시 맵에서 마지막 요소의 인덱스를 업데이트합니다.
getRandom () 1) 0부터 마지막 인덱스까지 난수를 생성합니다. 2) 무작위로 생성 된 인덱스에 배열 요소를 반환합니다.
search (x) 해시 맵에서 x를 검색합니다.
이것은 오래되었지만 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;
}
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 (또는 그 Dictionary 자손) 은이 문제에 대한 실제 해결책이 아닙니다. ) 동적 크기 조정 경계에서.
이 문제에 대한 실제 해결책이 있습니까? 내가 생각할 수있는 것은 Dictionary / Hashtable 초기 용량을 예상했던 것보다 훨씬 큰 규모로 지정하면 크기를 조정할 필요가 없기 때문에 O (1) 작업을 얻는 것입니다.
Java의 HashSet을 사용하여이 작업을 수행 할 수 없습니까? 기본적으로 O (1)에서 insert, del, search를 모두 제공합니다. getRandom의 경우 임의의 동작을 제공하는 Set의 반복자를 사용할 수 있습니다. 나머지 요소에 대해 걱정하지 않고 세트에서 첫 번째 요소를 반복 할 수 있습니다.
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`
}
}
무작위 요소를 찾기 위해 epoch % arraysize를 사용하지 않는 이유는 무엇입니까? 배열 크기를 찾는 것은 O (n)이지만 상각 복잡도는 O (1)입니다.
해시 테이블과 함께 이중 링크 목록을 사용할 수 있다고 생각합니다. 키는 요소이고 관련 값은 이중 링크 목록의 노드입니다.