ฉันได้รับปัญหานี้ในการสัมภาษณ์ คุณจะตอบว่าอย่างไร?
ออกแบบโครงสร้างข้อมูลที่มีการดำเนินการต่อไปนี้ใน O (1) เวลา:
- แทรก
- ลบ
- ประกอบด้วย
- รับองค์ประกอบแบบสุ่ม
ฉันได้รับปัญหานี้ในการสัมภาษณ์ คุณจะตอบว่าอย่างไร?
ออกแบบโครงสร้างข้อมูลที่มีการดำเนินการต่อไปนี้ใน O (1) เวลา:
คำตอบ:
พิจารณาโครงสร้างข้อมูลที่ประกอบด้วยแฮชแท็ก H และอาร์เรย์ A คีย์แฮชแท็กคือองค์ประกอบในโครงสร้างข้อมูลและค่าคือตำแหน่งในอาร์เรย์
เนื่องจากอาร์เรย์จำเป็นต้องเพิ่มขนาดโดยอัตโนมัติจึงต้องตัดจำหน่าย O (1) เพื่อเพิ่มองค์ประกอบ แต่ฉันคิดว่าไม่เป็นไร
hashtable.get((int)(Math.random()*hashtable.size()));
คุณอาจไม่ชอบสิ่งนี้เพราะพวกเขาอาจกำลังมองหาวิธีแก้ปัญหาที่ชาญฉลาด แต่บางครั้งมันก็จ่ายเพื่อยึดติดกับปืนของคุณ ... ตารางแฮชตอบสนองความต้องการอยู่แล้ว - อาจจะดีกว่าสิ่งอื่นใด (แม้ว่าจะเห็นได้ชัดว่าเป็นค่าคงที่ค่าตัดจำหน่าย เวลาและการประนีประนอมกับโซลูชันอื่น ๆ ที่แตกต่างกัน)
ข้อกำหนดที่ยุ่งยากคือการเลือก "องค์ประกอบแบบสุ่ม": ในตารางแฮชคุณจะต้องสแกนหรือตรวจสอบองค์ประกอบดังกล่าว
สำหรับการแฮชแบบปิด / ที่อยู่แบบเปิดโอกาสที่ที่เก็บข้อมูลใด ๆ จะถูกครอบครองsize() / capacity()
แต่โดยทั่วไปแล้วสิ่งสำคัญคือสิ่งนี้จะถูกเก็บไว้ในช่วงการคูณคงที่โดยการใช้งานตารางแฮช (เช่นตารางอาจมีขนาดใหญ่กว่าเนื้อหาปัจจุบันโดยพูดว่า 1.2x ถึง ~ 10x ขึ้นอยู่กับการปรับแต่งประสิทธิภาพ / หน่วยความจำ) ซึ่งหมายความว่าโดยเฉลี่ยแล้วเราสามารถคาดหวังว่าจะค้นหา 1.2 ถึง 10 ที่เก็บข้อมูลโดยไม่ขึ้นกับขนาดทั้งหมดของคอนเทนเนอร์ ตัดจำหน่าย O (1)
ฉันนึกภาพออกสองวิธีง่ายๆ (และอีกหลายวิธีที่ดีมาก):
ค้นหาเชิงเส้นจากที่เก็บข้อมูลแบบสุ่ม
ลองสุ่มที่เก็บข้อมูลซ้ำ ๆ จนกว่าคุณจะพบถังบรรจุ
ไม่ใช่วิธีแก้ปัญหาที่ยอดเยี่ยม แต่อาจยังเป็นการประนีประนอมโดยรวมที่ดีกว่าค่าใช้จ่ายด้านหน่วยความจำและประสิทธิภาพในการดูแลอาร์เรย์ดัชนีที่สองตลอดเวลา
ทางออกที่ดีที่สุดน่าจะเป็นตารางแฮช + อาร์เรย์มันเร็วและกำหนดได้จริง
แต่คำตอบที่ได้คะแนนต่ำที่สุด (เพียงแค่ใช้ตารางแฮช!) ก็ยอดเยี่ยมเช่นกัน!
ผู้คนอาจไม่ชอบสิ่งนี้เนื่องจาก "เป็นไปได้ไม่สิ้นสุดลูป" และฉันเคยเห็นคนฉลาด ๆ ก็มีปฏิกิริยาเช่นนี้เหมือนกัน แต่มันผิด! เหตุการณ์ที่ไม่น่าเกิดขึ้นอย่างไม่มีที่สิ้นสุดก็ไม่เกิดขึ้น
สมมติว่าพฤติกรรมที่ดีของแหล่งที่มาแบบสุ่มหลอกของคุณซึ่งไม่ยากที่จะสร้างขึ้นสำหรับพฤติกรรมนี้โดยเฉพาะและตารางแฮชนั้นเต็มอย่างน้อย 20% เสมอดูง่ายๆว่า:
จะไม่มีทางเกิดขึ้นได้ที่ getRandom () ต้องพยายามมากกว่า 1,000 ครั้ง เพียงแค่ไม่เคย อันที่จริงความน่าจะเป็นของเหตุการณ์ดังกล่าวคือ 0.8 ^ 1000 ซึ่งก็คือ 10 ^ -97 - ดังนั้นเราจึงต้องทำซ้ำ 10 ^ 88 ครั้งเพื่อให้มีโอกาสหนึ่งครั้งในพันล้านของเหตุการณ์นั้นเคยเกิดขึ้นครั้งเดียว แม้ว่าโปรแกรมนี้จะทำงานเต็มเวลาบนคอมพิวเตอร์ทุกเครื่องของมนุษยชาติจนกว่าดวงอาทิตย์จะสิ้นชีวิตสิ่งนี้ก็จะไม่เกิดขึ้น
สำหรับคำถามนี้ฉันจะใช้โครงสร้างข้อมูลสองแบบ
ขั้นตอน: -
รหัส: -
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 มาตรฐานอื่น ๆ ไม่ใช่ว่าคุณจะต้องนำไปใช้ในรายละเอียดในระหว่างการสัมภาษณ์ แต่ก็เป็นเรื่องดีที่จะมีแนวทางแก้ไขที่เป็นรูปธรรมให้ดู ...
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
มีข้อความ "มีการเพิ่มรายการที่มีคีย์เดียวกันแล้ว" จะถูกโยนทิ้ง (จากพจนานุกรมดัชนีพื้นฐาน)
เราสามารถใช้แฮชเพื่อสนับสนุนการดำเนินการในΘ (1) ครั้ง
แทรก (x) 1) ตรวจสอบว่ามี x อยู่แล้วหรือไม่โดยทำการค้นหาแผนที่แฮช 2) ถ้าไม่มีให้ใส่ที่ท้ายอาร์เรย์ 3) เพิ่มในตารางแฮชด้วย x จะถูกเพิ่มเป็นคีย์และดัชนีอาร์เรย์สุดท้ายเป็นดัชนี
ลบ (x) 1) ตรวจสอบว่ามี x อยู่หรือไม่โดยทำการค้นหาแผนที่แฮช 2) หากมีอยู่ให้ค้นหาดัชนีและลบออกจากแผนที่แฮช 3) สลับองค์ประกอบสุดท้ายกับองค์ประกอบนี้ในอาร์เรย์และลบองค์ประกอบสุดท้าย การสลับเสร็จสิ้นเนื่องจากสามารถลบองค์ประกอบสุดท้ายได้ในเวลา O (1) 4) อัปเดตดัชนีขององค์ประกอบสุดท้ายในแผนที่แฮช
getRandom () 1) สร้างตัวเลขสุ่มจาก 0 ถึงดัชนีสุดท้าย 2) ส่งคืนองค์ประกอบอาร์เรย์ที่ดัชนีที่สร้างขึ้นแบบสุ่ม
ค้นหา (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 (หรือลูกหลานในพจนานุกรม) ไม่ใช่วิธีแก้ปัญหาที่แท้จริงสำหรับปัญหานี้เนื่องจาก Put () สามารถตัดจำหน่ายได้เฉพาะ O (1) ไม่ใช่ O (1) จริงเนื่องจากเป็น O (N ) ที่ขอบเขตการปรับขนาดแบบไดนามิก
มีวิธีแก้ปัญหานี้จริงหรือไม่? สิ่งที่ฉันคิดได้ก็คือหากคุณระบุความจุเริ่มต้นของ Dictionary / Hashtable ตามลำดับขนาดที่เกินกว่าที่คุณคาดไว้ว่าจะต้องการคุณจะได้รับการดำเนินการ O (1) เนื่องจากคุณไม่จำเป็นต้องปรับขนาด
ฉันเห็นด้วยกับอานนท์ ยกเว้นข้อกำหนดสุดท้ายที่ต้องการองค์ประกอบแบบสุ่มที่มีความเป็นธรรมเท่าเทียมกันข้อกำหนดอื่น ๆ ทั้งหมดสามารถแก้ไขได้โดยใช้ DS ที่ใช้ Hash เดียว ฉันจะเลือก HashSet สำหรับสิ่งนี้ใน Java โมดูโลของรหัสแฮชขององค์ประกอบจะทำให้ฉันไม่มีดัชนีของอาร์เรย์พื้นฐานในเวลา O (1) ฉันสามารถใช้เพื่อเพิ่มลบและมีการดำเนินการได้
เราทำสิ่งนี้โดยใช้ HashSet ของ Java ไม่ได้หรือ มีการแทรกเดลค้นหาทั้งหมดใน O (1) ตามค่าเริ่มต้น สำหรับ getRandom เราสามารถใช้ iterator ของ 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)
ฉันคิดว่าเราสามารถใช้ลิงค์ลิสต์แบบทวีคูณกับตารางแฮชได้ คีย์จะเป็นองค์ประกอบและค่าที่เกี่ยวข้องจะเป็นโหนดในลิงค์ลิสต์แบบทวีคูณ