Tôi đã được đưa ra vấn đề này trong một cuộc phỏng vấn. Bạn sẽ trả lời như thế nào?
Thiết kế cấu trúc dữ liệu cung cấp các hoạt động sau trong thời gian O (1):
- chèn
- tẩy
- chứa đựng
- nhận phần tử ngẫu nhiên
Tôi đã được đưa ra vấn đề này trong một cuộc phỏng vấn. Bạn sẽ trả lời như thế nào?
Thiết kế cấu trúc dữ liệu cung cấp các hoạt động sau trong thời gian O (1):
Câu trả lời:
Hãy xem xét một cấu trúc dữ liệu bao gồm một bảng băm H và một mảng A. Các khóa bảng băm là các phần tử trong cấu trúc dữ liệu và các giá trị là vị trí của chúng trong mảng.
vì mảng cần tự động tăng kích thước, nó sẽ được khấu hao O (1) để thêm một phần tử, nhưng tôi đoán điều đó không sao.
Tra cứu O (1) ngụ ý một cấu trúc dữ liệu được băm .
Bằng cách so sánh:
hashtable.get((int)(Math.random()*hashtable.size()));
Bạn có thể không thích điều này, bởi vì họ có thể đang tìm kiếm một giải pháp thông minh, nhưng đôi khi bạn phải trả giá nếu dính vào súng của bạn ... Một bảng băm đã đáp ứng các yêu cầu - có lẽ tổng thể tốt hơn bất kỳ điều gì khác (mặc dù rõ ràng là không đổi phân bổ thời gian và với các thỏa hiệp khác nhau đối với các giải pháp khác).
Yêu cầu phức tạp là lựa chọn "phần tử ngẫu nhiên": trong một bảng băm, bạn sẽ cần phải quét hoặc thăm dò để tìm một phần tử như vậy.
Đối với địa chỉ băm / mở đóng, khả năng bất kỳ nhóm nào đã cho bị chiếm giữ là có size() / capacity()
, nhưng điều quan trọng là điều này thường được giữ trong một phạm vi nhân không đổi bằng cách triển khai bảng băm (ví dụ: bảng có thể được giữ lớn hơn nội dung hiện tại của nó bằng 1,2x đến ~ 10x tùy thuộc vào hiệu suất / điều chỉnh bộ nhớ). Điều này có nghĩa là trung bình chúng ta có thể mong đợi tìm kiếm 1,2 đến 10 thùng - hoàn toàn độc lập với tổng kích thước của thùng chứa; khấu hao O (1).
Tôi có thể tưởng tượng ra hai cách tiếp cận đơn giản (và nhiều cách tiếp cận khác lạ lùng hơn):
tìm kiếm tuyến tính từ một nhóm ngẫu nhiên
thử nhiều lần các nhóm ngẫu nhiên cho đến khi bạn tìm thấy một nhóm được điền
Không phải là một giải pháp tuyệt vời, nhưng vẫn có thể là một thỏa hiệp tổng thể tốt hơn so với chi phí bộ nhớ và hiệu suất của việc duy trì mảng chỉ mục thứ hai mọi lúc.
Giải pháp tốt nhất có lẽ là bảng băm + mảng, nó thực sự nhanh và xác định.
Nhưng câu trả lời được đánh giá thấp nhất (chỉ cần sử dụng bảng băm!) Cũng thực sự tuyệt vời!
Mọi người có thể không thích điều này vì "có thể có vòng lặp vô hạn", và tôi đã thấy những người rất thông minh cũng có phản ứng này, nhưng sai rồi! Các sự kiện không thể xảy ra vô hạn chỉ không xảy ra.
Giả sử hoạt động tốt của nguồn giả ngẫu nhiên của bạn - không khó để thiết lập cho hành vi cụ thể này - và bảng băm luôn đầy ít nhất 20%, thật dễ dàng để thấy rằng:
Sẽ không bao giờ xảy ra trường hợp getRandom () phải thử hơn 1000 lần. Chỉ là không bao giờ . Thật vậy, xác suất của một sự kiện như vậy là 0,8 ^ 1000, là 10 ^ -97 - vì vậy chúng ta sẽ phải lặp lại nó 10 ^ 88 lần để có một cơ hội trong một tỷ sự kiện xảy ra một lần. Ngay cả khi chương trình này được chạy toàn thời gian trên tất cả các máy tính của loài người cho đến khi Mặt trời chết, điều này sẽ không bao giờ xảy ra.
Đối với câu hỏi này, tôi sẽ sử dụng hai Cấu trúc dữ liệu
Các bước: -
Mã: -
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;
}
}
}
}
- Độ phức tạp thời gian O (1). - Độ phức tạp không gian O (N).
Đây là một giải pháp C # cho vấn đề đó mà tôi đã đưa ra cách đây ít lâu khi được hỏi cùng một câu hỏi. Nó thực hiện Thêm, Xóa, Chứa và Ngẫu nhiên cùng với các giao diện .NET tiêu chuẩn khác. Không phải là bạn sẽ cần phải thực hiện nó một cách chi tiết như vậy trong một cuộc phỏng vấn nhưng thật tuyệt khi có một giải pháp cụ thể để xem xét ...
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
với thông báo "Một mục có cùng khóa đã được thêm." sẽ được ném (từ Từ điển chỉ mục cơ bản).
Chúng tôi có thể sử dụng băm để hỗ trợ các hoạt động trong thời gian Θ (1).
insert (x) 1) Kiểm tra xem x đã có mặt hay chưa bằng cách thực hiện tra cứu bản đồ băm. 2) Nếu không có, hãy chèn nó vào cuối mảng. 3) Thêm vào bảng băm cũng được, x được thêm làm chỉ mục chính và chỉ số mảng cuối cùng làm chỉ mục.
remove (x) 1) Kiểm tra xem x có hiện diện hay không bằng cách thực hiện tra cứu bản đồ băm. 2) Nếu có, hãy tìm chỉ mục của nó và xóa nó khỏi bản đồ băm. 3) Hoán đổi phần tử cuối cùng với phần tử này trong mảng và xóa phần tử cuối cùng. Việc hoán đổi được thực hiện vì phần tử cuối cùng có thể được loại bỏ trong thời gian O (1). 4) Cập nhật chỉ mục của phần tử cuối cùng trong bản đồ băm.
getRandom () 1) Tạo một số ngẫu nhiên từ 0 đến chỉ mục cuối cùng. 2) Trả về phần tử mảng tại chỉ mục được tạo ngẫu nhiên.
tìm kiếm (x) Thực hiện tìm kiếm x trong bản đồ băm.
Mặc dù đây là cách cũ, nhưng vì không có câu trả lời trong C ++, đây là hai xu của tôi.
#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<<" ";
}
}
};
Đây là một đoạn mã khách hàng để kiểm tra giải pháp.
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;
}
Trong C # 3.0 + .NET Framework 4, giá trị chung Dictionary<TKey,TValue>
thậm chí còn tốt hơn Hashtable vì bạn có thể sử dụng System.Linq
phương thức mở rộng ElementAt()
để lập chỉ mục vào mảng động cơ bản nơi các KeyValuePair<TKey,TValue>
phần tử được lưu trữ:
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;
}
Tuy nhiên, theo như tôi biết, một Hashtable (hoặc con cháu của Từ điển) không phải là giải pháp thực sự cho vấn đề này vì Put () chỉ có thể được khấu hao O (1), không đúng O (1), vì nó là O (N ) ở ranh giới thay đổi kích thước động.
Có một giải pháp thực sự cho vấn đề này? Tất cả những gì tôi có thể nghĩ là nếu bạn chỉ định dung lượng ban đầu của Dictionary / Hashtable theo thứ tự độ lớn vượt quá những gì bạn dự đoán từng cần, thì bạn sẽ nhận được các phép toán O (1) vì bạn không bao giờ cần thay đổi kích thước.
Tôi đồng ý với Anon. Ngoại trừ yêu cầu cuối cùng trong đó yêu cầu nhận được một phần tử ngẫu nhiên với sự công bằng như nhau, tất cả các yêu cầu khác chỉ có thể được giải quyết bằng cách sử dụng một DS dựa trên băm duy nhất. Tôi sẽ chọn HashSet cho điều này trong Java. Mô-đun mã băm của một phần tử sẽ cung cấp cho tôi chỉ số không của mảng bên dưới trong thời gian O (1). Tôi có thể sử dụng nó cho các hoạt động thêm, bớt và chứa.
Chúng tôi không thể làm điều này bằng cách sử dụng HashSet của Java? Nó cung cấp chèn, xóa, tìm kiếm tất cả trong O (1) theo mặc định. Đối với getRandom, chúng ta có thể sử dụng trình lặp của Set mà luôn cho hành vi ngẫu nhiên. Chúng tôi chỉ cần lặp lại phần tử đầu tiên từ tập hợp mà không cần lo lắng về phần còn lại
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`
}
}
Tại sao chúng ta không sử dụng epoch% mảng để tìm phần tử ngẫu nhiên. Tìm kích thước mảng là O (n) nhưng độ phức tạp khấu hao sẽ là O (1).
Tôi nghĩ chúng ta có thể sử dụng danh sách liên kết kép với bảng băm. khóa sẽ là phần tử và giá trị liên kết của nó sẽ là nút trong danh sách liên kết kép.