[data-structures] 데이터 구조 : 삽입, 제거, 포함, 임의 요소 가져 오기, 모두 O (1)

인터뷰에서이 문제를 받았습니다. 어떻게 대답하겠습니까?

O (1) 시간에 다음 작업을 제공하는 데이터 구조를 설계합니다.

  • 끼워 넣다
  • 없애다
  • 포함
  • 임의의 요소를 얻다


답변

해시 테이블 H와 배열 A로 구성된 데이터 구조를 고려하십시오. 해시 테이블 키는 데이터 구조의 요소이고 값은 배열에서의 위치입니다.

  1. insert (value) : 값을 배열에 추가하고 i를 A의 인덱스로 둡니다. H [value] = i로 설정합니다.
  2. remove (value) : A의 값을 포함하는 셀을 A의 마지막 요소로 교체합니다. d를 인덱스 m에있는 배열 A의 마지막 요소로 둡니다. 제거 할 값의 배열에있는 인덱스 인 H [값]이되도록합니다. A [i] = d, H [d] = i를 설정하고 배열의 크기를 1만큼 줄이고 H에서 값을 제거합니다.
  3. contains (값) : H.contains (값) 반환
  4. getRandomElement () : let r = random (A의 현재 크기). 반환 A [r].

배열의 크기가 자동으로 증가해야하기 때문에 요소를 추가하기 위해 O (1)을 분할 할 것입니다.하지만 괜찮다고 생각합니다.


답변

O (1) 조회는 해시 된 데이터 구조를 의미합니다 .

비교 :

  • O (N) 조회로 O (1) 삽입 / 삭제는 연결 목록을 의미합니다.
  • O (1) 삽입, O (N) 삭제 및 O (N) 조회는 배열 지원 목록을 의미합니다.
  • O (logN) 삽입 / 삭제 / 조회는 트리 또는 힙을 의미합니다.

답변

아마도 현명한 솔루션을 찾고 있기 때문에 이것을 좋아하지 않을 수도 있지만 때로는 총을 고수하는 것이 좋습니다 … 해시 테이블은 이미 요구 사항을 충족합니다 -아마도 다른 어떤 것보다 전반적으로 더 좋습니다 (분명히 상각 상수 시간 및 다른 솔루션에 대한 다른 타협).

까다로운 요구 사항은 “무작위 요소”선택입니다. 해시 테이블에서 이러한 요소를 스캔하거나 조사해야합니다.

폐쇄 형 해싱 / 개방형 주소 지정의 경우 주어진 버킷이 점유 될 가능성은 size() / capacity()이지만 결정적으로 이것은 일반적으로 해시 테이블 구현에 의해 일정한 곱셈 범위로 유지됩니다 (예 : 테이블이 현재 내용보다 예를 들어 1.2 배 더 크게 유지 될 수 있음). 성능 / 메모리 조정에 따라 ~ 10x). 이는 평균적으로 컨테이너의 전체 크기와 완전히 독립적 인 1.2 ~ 10 개의 버킷을 검색 할 수 있음을 의미합니다. 상각 된 O (1).

나는 두 가지 간단한 접근 방식을 상상할 수 있습니다 (그리고 훨씬 더 어리석은 접근 방식).

  • 임의 버킷에서 선형 검색

    • 비어있는 / 가치 보유 버킷을 고려하십시오. “–AC —– B–D”: B를 선호하더라도 첫 번째 “무작위”선택이 공정하다고 말할 수 있습니다 . B는 더 이상 선호 될 가능성이 없기 때문입니다. 그러나 동일한 값을 사용하여 “무작위”선택을 반복하는 경우 B를 반복적으로 선호하는 것은 바람직하지 않을 수 있습니다 (질문에서 확률조차 요구하지 않음).
  • 채워진 버킷을 찾을 때까지 무작위 버킷을 반복적으로 시도하십시오.

    • “only”capacity () / size () 평균 버킷 방문 (위와 같음)-그러나 실제적으로는 난수 생성이 상대적으로 비싸기 때문에 더 비싸고, 무한히 가능성이없는 최악의 행동이라면 무한히 나쁩니다 …
      • 더 빠른 타협은 무작위로 선택한 초기 버킷에서 미리 생성 된 임의 오프셋 목록을 사용하여 버킷 수에 % -ing하는 것입니다.

훌륭한 솔루션은 아니지만 항상 두 번째 인덱스 배열을 유지 관리하는 메모리 및 성능 오버 헤드보다 전반적으로 더 나은 타협점 일 수 있습니다.


답변

가장 좋은 해결책은 아마도 해시 테이블 + 배열 일 것입니다. 정말 빠르고 결정적입니다.

그러나 가장 낮은 등급의 답변 (해시 테이블을 사용하십시오!)도 실제로 훌륭합니다!

  • 재해 싱이 포함 된 해시 테이블 또는 새 버킷 선택 (예 : 버킷 당 하나의 요소, 연결 목록 없음)
  • getRandom ()은 비어있을 때까지 임의의 버킷을 반복적으로 선택하려고합니다.
  • Fail-safe로, 아마도 getRandom (), N (요소 수) 시도 실패 후 [0, N-1]에서 임의의 인덱스 i를 선택한 다음 해시 테이블을 선형으로 살펴보고 # i- 번째 요소를 선택합니다. .

사람들은 “가능한 무한 루프”때문에 이것을 좋아하지 않을 수 있습니다. 그리고 저는 매우 똑똑한 사람들도 이런 반응을 보이는 것을 보았지만 그것은 틀 렸습니다! 무한히 가능성이없는 이벤트 발생 하지 않습니다 .

이 특정 동작에 대해 설정하기 어렵지 않은 의사 랜덤 소스의 좋은 동작과 해시 테이블이 항상 최소 20 % 가득 차 있다고 가정하면 다음을 쉽게 알 수 있습니다.

getRandom ()이 1000 번 이상 시도 하지 않아도 됩니다 . 그냥 결코 . 실제로 그러한 사건의 확률은 0.8 ^ 1000, 즉 10 ^ -97입니다. 그래서 우리는 그것을 10 ^ 88 번 반복해야 10 억분의 1의 기회가 한 번 발생합니다. 이 프로그램이 태양이 죽을 때까지 인류의 모든 컴퓨터에서 풀 타임으로 실행 되더라도 결코 일어나지 않을 것입니다.


답변

이 질문에는 두 개의 데이터 구조를 사용합니다.

  • HashMap
  • ArrayList / Array / Double LinkedList.

단계 :-

  1. 삽입 :-X가 이미 HashMap에 있는지 확인하십시오 .– 시간 복잡도 O (1). 존재하지 않는 경우 ArrayList의 끝에 추가-시간 복잡도 O (1). HashMap에 x를 키로 추가하고 마지막 Index를 값으로 추가하십시오-시간 복잡도 O (1).
  2. 제거 :-X가 HashMap에 있는지 확인-시간 복잡도 O (1). 존재하는 경우 인덱스를 찾아 HashMap-시간 복잡도 O (1)에서 제거하십시오. 이 요소를 ArrayList의 마지막 요소로 바꾸고 마지막 요소 인 시간 복잡도 O (1)를 제거합니다. HashMap-시간 복잡도 O (1)에서 마지막 요소의 색인을 업데이트합니다.
  3. GetRandom :-0부터 ArrayList의 마지막 인덱스까지 난수를 생성합니다. 생성 된 임의 인덱스에서 ArrayList 요소를 반환합니다 .– 시간 복잡도 O (1).
  4. 검색 :-HashMap에서 x를 키로 참조하십시오. -시간 복잡도 O (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&lt;T&gt;"/> class.
    /// </summary>
    public Bag()
        : this(0)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Bag&lt;T&gt;"/> 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&lt;T&gt;"/> 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;

        }
    }
}


답변

해싱을 사용하여 Θ (1) 시간에 작업을 지원할 수 있습니다.

insert (x)
1) 해시 맵 조회를 수행하여 x가 이미 있는지 확인합니다. 2) 없으면 배열 끝에 삽입합니다. 3) 해시 테이블도 추가하면 x는 키로 추가되고 마지막 배열 인덱스는 인덱스로 추가됩니다.

remove (x)
1) 해시 맵 조회를 수행하여 x가 있는지 확인합니다. 2) 존재하는 경우 색인을 찾아 해시 맵에서 제거하십시오. 3) 마지막 요소를 배열의이 요소로 바꾸고 마지막 요소를 제거합니다. 마지막 요소는 O (1) 시간에 제거 할 수 있기 때문에 스와핑이 수행됩니다. 4) 해시 맵에서 마지막 요소의 인덱스를 업데이트합니다.

getRandom ()
1) 0부터 마지막 ​​인덱스까지 난수를 생성합니다. 2) 무작위로 생성 된 인덱스에 배열 요소를 반환합니다.

search (x)
해시 맵에서 x를 검색합니다.