Реалізуйте чергу, в якій push_rear (), pop_front () та get_min () - це постійні операції часу


76

Я зіткнувся з цим запитанням: Реалізуйте чергу, в якій push_rear (), pop_front () та get_min () - це постійні операції часу.

Спочатку я думав використовувати структуру даних min-heap, яка має складність O (1) для get_min (). Але push_rear () і pop_front () будуть O (log (n)).

Хтось знає, що було б найкращим способом реалізувати таку чергу, яка має O (1) push (), pop () і min ()?

Я погуглив про це, і хотів вказати на цю нитку "Алгоритм виродків" . Але, схоже, жодне з рішень не дотримується правила постійного часу для всіх 3 методів: push (), pop () та min ().

Дякую за всі пропозиції.


Чи добре у вас із амортизованими межами часу O (1) для всього цього, чи це повинні бути найгірші часові межі?
templatetypedef

Я не впевнений, це запитання щодо інтерв’ю в Google, спочатку я його бачив на веб- сайті careercup.com/question?id=7263132 .... Здається, це запитання означало найгірші часові межі. Це здається неможливим?
біти

@ bits- Ні, це, безумовно, здається можливим, і я відмовляюся від цього прямо зараз. :-) Я розглядав використання декартових дерев для цього - це дає вам амортизовану вставку O (1) та пошук O (1), і я майже працював з амортизованим видаленням O (1). Але якщо ви шукаєте найгірші межі, я зміню свій підхід.
templatetypedef

добре, зараз дивимось на відповідь Кдото нижче; Зараз я впевнений, що найгірші межі можуть бути не можливим. Тож, можливо, працівники Google повинні шукати амортизований O (1). РЕДАГУВАТИ: добре, як покажчик templatetypedef у коментарях до відповіді Кдото, доказ невірний. Відзначено.
біти

Не будьте настільки впевнені, мій доказ був неправильним. Однак я не думаю, що O (1) був знайдений для всіх операцій, амортизований чи ні. І я підозрюю, що це неможливо.
Ольховський

Відповіді:


99

Ви можете реалізувати стек за допомогою O (1) pop (), push () та get_min (): просто збережіть поточний мінімум разом із кожним елементом. Так, наприклад, [4,2,5,1]стає стек (1 зверху) [(4,4), (2,2), (5,2), (1,1)].

Тоді ви можете використовувати два стеки для реалізації черги . Натисніть на одну стопку, перейдіть з іншої; якщо другий стек порожній під час вискакування, перемістіть усі елементи з першого стека у другий.

Наприклад, для popзапиту, переміщуючи всі елементи з першого стеку [(4,4), (2,2), (5,2), (1,1)], другий стек буде [(1,1), (5,1), (2,1), (4,1)]. а тепер поверніть верхній елемент з другого стека.

Щоб знайти мінімальний елемент черги, перегляньте найменші два елементи окремих мін-стеків, а потім візьміть мінімум з цих двох значень. (Звичайно, тут є додаткова логіка, якщо один із стеків порожній, але це не надто складно обійти).

Він буде мати O (1) get_min()і push()і амортизовану O (1) pop().


10
Як використання двох стеків для реалізації черги дає вам амортизований поп O (1)?
templatetypedef

7
@template Кожен елемент можна перенести з одного стека в інший лише один раз.
adamax

6
Якщо ви зберігаєте "поточний мінімум разом з елементами" і висуваєте мінімум із черги, як би ви знали, що таке новий мінімум, за час O (1)?
Ольховський

4
@adamax Я не можу зрозуміти 3-ю частину. Як працює ваш мінімум. Як бачите, тут забагато коментарів. Чому б просто не навести невеликий приклад зі своїми кроками алгоритмів. Це допоможе зрозуміти ваш алгоритм.
UmmaGumma

7
@adamax Я нарешті розумію ваше рішення. Ви повинні додати до свого пояснення, що ми повинні перераховувати значення других елементів при переміщенні елементів з першої структури у другу. До речі, як я показую у своїй відповіді, можна робити всі ці операції в o (1), а не в амортизованій O (1). :)
UmmaGumma

28

Гаразд - я думаю, у мене є відповідь, яка дає вам усі ці операції в амортизованому O (1), тобто будь-яка одна операція може тривати до O (n), але будь-яка послідовність n операцій займає O (1) час на операцію .

Ідея полягає в тому, щоб зберігати ваші дані у вигляді декартового дерева . Це бінарне дерево, що підпорядковується властивості min-heap (кожен вузол не більший за його дочірні елементи) і упорядкований таким чином, що обхідне обертання вузлів повертає вам вузли в тому ж порядку, в якому вони були додані. Наприклад, ось декартове дерево для послідовності 2 1 4 3 5:

       1
     /   \
    2      3
          / \
         4   5

Можна вставити елемент у декартове дерево за амортизований час O (1), використовуючи наступну процедуру. Подивіться на правий хребет дерева (шлях від кореня до самого правого листя, утворений постійним кроком праворуч). Починаючи з самого правого вузла, проскануйте вгору по цьому шляху, поки не знайдете перший вузол, менший за вузол, який ви вставляєте.
Змініть цей вузол так, щоб його правий дочірній елемент був новим вузлом, а потім зробіть колишній правий дочірній елемент цього вузла лівим дочірнім елементом вузла, який ви щойно додали. Наприклад, припустимо, що ми хочемо вставити ще одну копію 2 у вищезазначене дерево. Ми йдемо правою хребтом повз 5 і 3, але зупиняємось нижче 1, тому що 1 <2. Потім ми міняємо дерево так, щоб виглядати так:

       1
     /   \
    2      2
          /
         3
        / \
       4   5

Зверніть увагу, що обхід в обробці дає 2 1 4 3 5 2, тобто послідовність, в якій ми додали значення.

Це працює в амортизованому O (1), оскільки ми можемо створити потенційну функцію, рівну кількості вузлів у правій частині дерева. Реальний час, необхідний для вставки вузла, дорівнює 1 плюс кількість вузлів у хребті, який ми розглядаємо (називайте це k). Після того, як ми знайдемо місце для вставки вузла, розмір хребта зменшується на довжину k - 1, оскільки кожен з k-х вузлів, які ми відвідали, перебуває вже не на правому хребті, і новий вузол знаходиться на своєму місці. Це дає амортизовану вартість 1 + k + (1 - k) = 2 = O (1) для амортизованої вставки O (1). Як інший спосіб думати про це, коли вузол переміщений з правого відділу хребта, він більше ніколи не буде частиною правого відділу хребта, і тому нам більше ніколи не доведеться його переміщати. Оскільки кожен з n вузлів можна переміщати не більше одного разу, це означає, що n вставок може робити не більше n переміщень,

Щоб виконати крок із зміни черги, ми просто видаляємо крайній лівий вузол з декартового дерева. Якщо цей вузол є листом, ми закінчили. В іншому випадку вузол може мати лише одну дочірню організацію (правильну дочірню дитину), і тому ми замінюємо вузол на її правильну дочірню організацію. За умови, що ми відстежуємо, де знаходиться крайній лівий вузол, цей крок займає час O (1). Однак, видаливши крайній лівий вузол і замінивши його правим дочірнім, ми могли б не знати, де знаходиться новий крайній лівий вузол. Щоб це виправити, ми просто спускаємося по лівому хребту дерева, починаючи з нового вузла, який ми щойно перенесли до крайньої лівої дочірньої дитини. Я стверджую, що це все ще триває за амортизованим часом O (1). Щоб побачити це, я стверджую, що вузол відвідується щонайбільше один раз під час будь-якого з цих проходів, щоб знайти крайній лівий вузол. Щоб побачити це, зверніть увагу, що після того, як вузол був відвіданий таким чином, єдиний спосіб, який нам коли-небудь знадобиться подивитися на це, - це якщо він буде переміщений з дочірнього самого лівого вузла в самий лівий вузол. Але всі відвідані вузли є батьками самого лівого вузла, тому цього не може статися. Отже, кожен вузол відвідується щонайбільше один раз під час цього процесу, і поп працює в O (1).

Ми можемо знайти min-min в O (1), оскільки декартове дерево дає нам доступ до найменшого елемента дерева безкоштовно; це корінь дерева.

Нарешті, щоб побачити, що вузли повертаються в тому самому порядку, в якому вони були вставлені, зауважте, що декартове дерево завжди зберігає свої елементи так, що обхідне обертання відвідує їх у відсортованому порядку. Оскільки ми завжди видаляємо крайній лівий вузол на кожному кроці, і це перший елемент обходу inorder, ми завжди отримуємо вузли назад у тому порядку, в якому вони були вставлені.

Коротше кажучи, ми отримуємо амортизований O (1) поштовх і поп, і O (1) найгірший варіант знаходження-min.

Якщо я зможу придумати найгірший варіант реалізації O (1), я точно його опублікую. Це була велика проблема; дякую за розміщення!


Я все ще розглядаю, чи справді ваш поп амортизується O (1). Я обов'язково підтримую цю відповідь, коли підтверджу. Було б непогано, якби хтось інший допоміг перевірити і цю відповідь.
Ольховський

@ Kdoto - Подумавши про це, вам потрібно, щоб кожен вузол зберігав батьківський вказівник, якщо ви хочете отримати амортизовану девіую O (1), оскільки таким чином, видаляючи лист, ви можете оновити вказівник на крайній лівий вузол у дерево в гіршому випадку O (1).
templatetypedef

Я бачу, що ваша реалізація keithschwarz.com/interesting/code/?dir=min-queue // додавати та видаляти з черги дуже чітко, але знайти min не зрозумілий з двома старими та новими стеками? для пошуку хв ви використовуєте іншу структуру? не могли б ви навести незначний приклад, як це працює?
Mio Unio

23

Гаразд, ось одне рішення.

Спочатку нам потрібні речі, які забезпечують push_back (), push_front (), pop_back () та pop_front () в 0 (1). Це легко реалізувати за допомогою масиву та 2 ітераторів. Перший ітератор вказуватиме спереду, другий назад. Давайте назвемо такі речі деке.

Ось псевдокод:

class MyQueue//Our data structure
{
    deque D;//We need 2 deque objects
    deque Min;

    push(element)//pushing element to MyQueue
    {
        D.push_back(element);
        while(Min.is_not_empty() and Min.back()>element)
             Min.pop_back();
        Min.push_back(element);
    }
    pop()//poping MyQueue
    {
         if(Min.front()==D.front() )
            Min.pop_front();
         D.pop_front();
    }

    min()
    {
         return Min.front();
    }
}

Пояснення:

Приклад давайте просунемо числа [12,5,10,7,11,19] і до нашої MyQueue

1) штовхання 12

D [12]
Min[12]

2) штовхання 5

D[12,5]
Min[5] //5>12 so 12 removed

3) штовхання 10

D[12,5,10]
Min[5,10]

4) штовхання 7

D[12,5,10,7]
Min[5,7]

6) штовхання 11

D[12,5,10,7,11]
Min[5,7,11]

7) штовхання 19

D[12,5,10,7,11,19]
Min[5,7,11,19]

Тепер зателефонуємо pop_front ()

ми отримали

 D[5,10,7,11,19]
 Min[5,7,11,19]

Мінімум - 5

Давайте знову зателефонуємо pop_front ()

Пояснення: pop_front видалить 5 з D, але він також виведе передній елемент Min, оскільки це дорівнює передньому елементу D (5).

 D[10,7,11,19]
 Min[7,11,19]

А мінімум - 7. :)


Здається, якщо натиснути 2, 3, 1, то get_min повертає 2 замість 1.
adamax

@adamax На жаль :). Ти мене зрозумів. Я виправив push (). Зараз він працює коректно, але не в 0 (1). Він працює в амортизованому O (1), як ваш :).
UmmaGumma

2
@ UmmaGumma, гарна робота! Хоча незначна корекція, її 5 <12, коли 12 вискакує з стека.
шукач

3

Використовуйте один дека (A) для зберігання елементів, а інший Deque (B) - для зберігання мінімумів.

Коли х виставлено в чергу, натисніть_back до A і продовжуйте pop_backing B до тих пір, поки тил B не стане менше x, а потім push_back x до B.

при декекуванні A, pop_front A як значення, що повертається, а якщо воно дорівнює фронту B, pop_front B також.

отримуючи мінімум A, використовуйте передню частину B як значення, що повертається.

dequeue і getmin, очевидно, O (1). Для операції чергування розглянемо push_back з n елементів. Існує n push_back до A, n push_back до B і не більше n pop_back B, тому що кожен елемент або залишиться в B, або вискакується один раз з B. Взагалі є операції O (3n), а тому амортизована вартість становить O (1), а також для черги.

Нарешті, причина того, що цей алгоритм працює, полягає в тому, що коли ви ставите в чергу x до A, якщо в B є елементи, більші за x, вони ніколи не будуть мінімальними, оскільки x залишатиметься в черзі A довше, ніж будь-які елементи в B (черга є FIFO). Тому нам потрібно висунути елементи в B (з тильної сторони), які перевищують x, перш ніж вкласти x у B.

from collections import deque


class MinQueue(deque):
    def __init__(self):
        deque.__init__(self)
        self.minq = deque()

    def push_rear(self, x):
        self.append(x)
        while len(self.minq) > 0 and self.minq[-1] > x:
            self.minq.pop()
        self.minq.append(x)

    def pop_front(self):
        x = self.popleft()
        if self.minq[0] == x:
            self.minq.popleft()
        return(x)

    def get_min(self):
        return(self.minq[0])

1

Якщо ви не проти зберегти трохи зайвих даних, зберігати мінімальне значення має бути тривіально. Push and pop може оновити значення, якщо новий або видалений елемент є мінімальним, а повернення мінімального значення таке ж просте, як отримання значення змінної.

Це передбачається, що get_min () не змінює дані; якщо вам більше подобається щось на зразок pop_min () (тобто видалити мінімальний елемент), ви можете просто зберегти вказівник на фактичний елемент та елемент, що передує йому (якщо такий є), і відповідно оновити їх за допомогою push_rear () та pop_front () також.

Редагувати після коментарів:

Очевидно, це призводить до O (n) push and pop у випадку, якщо мінімум змін на цих операціях, і тому не відповідає строго вимогам.


1
Хіба це не дає вам O (n) поп, оскільки вам потрібно просканувати всі елементи, щоб знайти новий хв?
templatetypedef

2
Я думаю, що get_min () насправді не виводить дані. Але pop_front () дійсно видає дані. Скажімо, передній вузол - це також мінімальний вузол, тому він з’явився. Тепер, як ми можемо підтримувати властивість min у постійний час?
біти

Ах, хороший дзвінок ... хоча ти маєш рацію, @bits, це лише O (n) у тому випадку, якщо ти натискаєш новий мінімум або висуваєш свій поточний мінімум. Якщо це має бути гірший випадок O (1), я не знаю, що це можливо, але я хотів би бачити інакше.
Andy Mikula

1

Рішення цього питання, включаючи код, можна знайти тут: http://discuss.joelonsoftware.com/default.asp?interview.11.742223.32


Посилання на зовнішні сторінки не корисні. Що робити, якщо посилання розривається? Також: сторінка, на яку ви посилаєтесь, - це просто довга дискусія. Хороша відповідь охопила б важливі елементи цієї дискусії і залишила б пуху.
Річард

1

Ви можете насправді використовувати LinkedList для обслуговування черги.

Кожен елемент у LinkedList буде типу

class LinkedListElement
{
   LinkedListElement next;
   int currentMin;
}

Ви можете мати два покажчики Один вказує на Початок, а інші вказує на Кінець.

Якщо ви додасте елемент на початок черги. Вивчіть покажчик «Пуск» і вузол, який потрібно вставити. Якщо вузол для вставки currentmin менше, ніж start currentmin, вузол для вставки currentmin є мінімальним. В іншому випадку оновіть currentmin за допомогою start currentmin.

Повторіть те ж саме для enque.


0
#include <iostream>
#include <queue>
#include <deque>
using namespace std;

queue<int> main_queue;
deque<int> min_queue;

void clearQueue(deque<int> &q)
{
  while(q.empty() == false) q.pop_front();
}

void PushRear(int elem)
{
  main_queue.push(elem);

  if(min_queue.empty() == false && elem < min_queue.front())
  {
      clearQueue(min_queue);
  }

  while(min_queue.empty() == false && elem < min_queue.back())
  {
      min_queue.pop_back();
  }

  min_queue.push_back(elem);
}

void PopFront() 
{
  int elem = main_queue.front();
  main_queue.pop();

  if (elem == min_queue.front())
  {
       min_queue.pop_front();
  }
}

int GetMin() 
{ 
  return min_queue.front(); 
}

int main()
{
  PushRear(1);
  PushRear(-1);
  PushRear(2);

  cout<<GetMin()<<endl;
  PopFront();
  PopFront();
  cout<<GetMin()<<endl;

  return 0;
}

Не добре розміщувати поштовий індекс без супровідного, чітко сформульованого пояснення, чому код правильний.
Річард

Цей код дуже зрозумілий. Якщо ви хочете отримати пояснення, ви можете запитати замість того, щоб голосувати проти, будь ласка?
TheMan

Однією з якостей, яка мені найбільше подобається у StackOverflow, є висока якість відповідей тут. Коли я відвідую інші сайти, здається, що кожен, хто публікує публікації, просто підкидає пачки "коду, що пояснює себе", як ваш. Неминуче деякі з них помиляються, і кожному потрібен час, щоб зрозуміти та перевірити. Хороша відповідь проводить вас через процес перевірки та попереджуюче відповідає на ваші запитання. Важко пригадати, щоб повернутися і перевірити ці речі, тому я вважаю за краще голосувати проти, а потім нейтралізувати або проголосувати.
Річард

AFAICT, це той самий алгоритм, що вже поданий як вихідний код і описаний Цзяньглаєм більш ніж місяцем раніше.
j_random_hacker

0

Це рішення містить 2 черги:
1. main_q - зберігає введені номери.
2. min_q - зберігає мінімальні номери за певними правилами, які ми описамо (з’являються у функціях MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ()).

Ось код у Python. Черга реалізована за допомогою списку.
Основна ідея полягає у функціях MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ().
Одним з ключових припущень є те, що спорожнення черги займає значення o (0).
В кінці наводиться тест.

import numbers

class EmptyQueueException(Exception):
    pass

class BaseQ():
    def __init__(self):
        self.l = list()

    def enqueue(self, x):
        assert isinstance(x, numbers.Number)
        self.l.append(x)

    def dequeue(self):
        return self.l.pop(0)

    def peek_first(self):
        return self.l[0]

    def peek_last(self):
        return self.l[len(self.l)-1]

    def empty(self):
        return self.l==None or len(self.l)==0

    def clear(self):
        self.l=[]

class MainQ(BaseQ):
    def __init__(self, min_q):
        super().__init__()
        self.min_q = min_q

    def enqueue(self, x):
        super().enqueue(x)
        if self.min_q.empty():
            self.min_q.enqueue(x)
        elif x > self.min_q.peek_last():
            self.min_q.enqueue(x)
        else: # x <= self.min_q.peek_last():
            self.min_q.clear()
            self.min_q.enqueue(x)

    def dequeue(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty")
        x = super().dequeue()
        if x == self.min_q.peek_first():
            self.min_q.dequeue()
        return x

    def get_min(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty, NO minimum")
        return self.min_q.peek_first()

INPUT_NUMS = (("+", 5), ("+", 10), ("+", 3), ("+", 6), ("+", 1), ("+", 2), ("+", 4), ("+", -4), ("+", 100), ("+", -40),
              ("-",None), ("-",None), ("-",None), ("+",-400), ("+",90), ("-",None),
              ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None))

if __name__ == '__main__':
    min_q = BaseQ()
    main_q = MainQ(min_q)

    try:
        for operator, i in INPUT_NUMS:
            if operator=="+":
                main_q.enqueue(i)
                print("Added {} ; Min is: {}".format(i,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
            else:
                x = main_q.dequeue()
                print("Removed {} ; Min is: {}".format(x,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
    except Exception as e:
        print("exception: {}".format(e))

Результатом вищевказаного тесту є:

"C:\Program Files\Python35\python.exe" C:/dev/python/py3_pocs/proj1/priority_queue.py
Added 5 ; Min is: 5
main_q = [5]
min_q = [5]
==========
Added 10 ; Min is: 5
main_q = [5, 10]
min_q = [5, 10]
==========
Added 3 ; Min is: 3
main_q = [5, 10, 3]
min_q = [3]
==========
Added 6 ; Min is: 3
main_q = [5, 10, 3, 6]
min_q = [3, 6]
==========
Added 1 ; Min is: 1
main_q = [5, 10, 3, 6, 1]
min_q = [1]
==========
Added 2 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2]
min_q = [1, 2]
==========
Added 4 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2, 4]
min_q = [1, 2, 4]
==========
Added -4 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4]
min_q = [-4]
==========
Added 100 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100]
min_q = [-4, 100]
==========
Added -40 ; Min is: -40
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 5 ; Min is: -40
main_q = [10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 10 ; Min is: -40
main_q = [3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 3 ; Min is: -40
main_q = [6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Added -400 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400]
min_q = [-400]
==========
Added 90 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 6 ; Min is: -400
main_q = [1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 1 ; Min is: -400
main_q = [2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 2 ; Min is: -400
main_q = [4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 4 ; Min is: -400
main_q = [-4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed -4 ; Min is: -400
main_q = [100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 100 ; Min is: -400
main_q = [-40, -400, 90]
min_q = [-400, 90]
==========
Removed -40 ; Min is: -400
main_q = [-400, 90]
min_q = [-400, 90]
==========
Removed -400 ; Min is: 90
main_q = [90]
min_q = [90]
==========
exception: Queue is empty, NO minimum

Process finished with exit code 0

0

Впровадження Java

import java.io.*;
import java.util.*;

public class queueMin {
    static class stack {

        private Node<Integer> head;

        public void push(int data) {
            Node<Integer> newNode = new Node<Integer>(data);
            if(null == head) {
                head = newNode;
            } else {
                Node<Integer> prev = head;
                head = newNode;
                head.setNext(prev);
            }
        }

        public int pop() {
            int data = -1;
            if(null == head){
                System.out.println("Error Nothing to pop");
            } else {
                data = head.getData();
                head = head.getNext();
            }

            return data;
        }

        public int peek(){
            if(null == head){
                System.out.println("Error Nothing to pop");
                return -1;
            } else {
                return head.getData();
            }
        }

        public boolean isEmpty(){
            return null == head;
        }
    }

    static class stackMin extends stack {
        private stack s2;

        public stackMin(){
            s2 = new stack();
        }

        public void push(int data){
            if(data <= getMin()){
                s2.push(data);
            }

            super.push(data);
        }

        public int pop(){
            int value = super.pop();
            if(value == getMin()) {
                s2.pop();
            }
            return value;
        }

        public int getMin(){
            if(s2.isEmpty()) {
                return Integer.MAX_VALUE;
            }
            return s2.peek();
        }
    }

     static class Queue {

        private stackMin s1, s2;

        public Queue(){
            s1 = new stackMin();
            s2 = new stackMin();
        }

        public  void enQueue(int data) {
            s1.push(data);
        }

        public  int deQueue() {
            if(s2.isEmpty()) {
                while(!s1.isEmpty()) {
                    s2.push(s1.pop());
                }
            }

            return s2.pop();
        }

        public int getMin(){
            return Math.min(s1.isEmpty() ? Integer.MAX_VALUE : s1.getMin(), s2.isEmpty() ? Integer.MAX_VALUE : s2.getMin());
        }

    }



   static class Node<T> {
        private T data;
        private T min;
        private Node<T> next;

        public Node(T data){
            this.data = data;
            this.next = null;
        }


        public void setNext(Node<T> next){
            this.next = next;
        }

        public T getData(){
            return this.data;
        }

        public Node<T> getNext(){
            return this.next;
        }

        public void setMin(T min){
            this.min = min;
        }

        public T getMin(){
            return this.min;
        }
    }

    public static void main(String args[]){
       try {
           FastScanner in = newInput();
           PrintWriter out = newOutput();
          // System.out.println(out);
           Queue q = new Queue();
           int t = in.nextInt();
           while(t-- > 0) {
               String[] inp = in.nextLine().split(" ");
               switch (inp[0]) {
                   case "+":
                       q.enQueue(Integer.parseInt(inp[1]));
                       break;
                   case "-":
                       q.deQueue();
                       break;
                   case "?":
                       out.println(q.getMin());
                   default:
                       break;
               }
           }
           out.flush();
           out.close();

       } catch(IOException e){
          e.printStackTrace();
       }
    }

    static class FastScanner {
        static BufferedReader br;
        static StringTokenizer st;

        FastScanner(File f) {
            try {
                br = new BufferedReader(new FileReader(f));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        public FastScanner(InputStream f) {
            br = new BufferedReader(new InputStreamReader(f));
        }
        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        String nextLine(){
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
        long nextLong() {
            return Long.parseLong(next());
        }
        double nextDoulbe() {
            return Double.parseDouble(next());
        }
    }

    static FastScanner newInput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new FastScanner(new File("input.txt"));
        } else {
            return new FastScanner(System.in);
        }
    }
    static PrintWriter newOutput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new PrintWriter("output.txt");
        } else {
            return new PrintWriter(System.out);
        }
    }
}

0

Впровадження JavaScript

(Кредит на рішення adamax в за ідею, я вільно . На основі реалізацію на ньому Перейти до основи , щоб побачити повністю прокоментований код або читати через загальні кроки нижче Зауважимо , що це знаходить. Максимальне значення в O (1) постійна часу , а не мінімальне значення --easy змінити вгору):

Загальна ідея полягає в тому, щоб створити два стеки при побудові MaxQueue(я використовував зв’язаний список як основну Stackструктуру даних - не входить до коду; але будь-який Stackбуде робити, доки він реалізований із вставкою / видаленням O (1)). Одну з них ми будемо здебільшого popз ( dqStack), а одну - здебільшого push( eqStack).


Вставка: O (1) найгірший випадок

Бо enqueue, якщо значення MaxQueueпорожнє, ми будемо pushвикористовувати значення dqStackразом із поточним максимальним значенням у кортежі (те саме значення, оскільки це єдине значення в MaxQueue); наприклад:

const m = new MaxQueue();

m.enqueue(6);

/*
the dqStack now looks like:
[6, 6] - [value, max]
*/

Якщо MaxQueueне порожньо, pushтільки значення для eqStack;

m.enqueue(7);
m.enqueue(8);

/*
dqStack:         eqStack: 8
         [6, 6]           7 - just the value
*/

потім оновіть максимальне значення в кортежі.

/*
dqStack:         eqStack: 8
         [6, 8]           7
*/


Видалення: O (1) амортизовано

Бо dequeueми будемо popотримувати dqStackі повертати значення з кортежу.

m.dequeue();
> 6

// equivalent to:
/*
const tuple = m.dqStack.pop() // [6, 8]
tuple[0];
> 6
*/

Тоді, якщо dqStackпорожньо, перемістити всі значення eqStackдо dqStack, наприклад:

// if we build a MaxQueue
const maxQ = new MaxQueue(3, 5, 2, 4, 1);

/*
the stacks will look like:

dqStack:         eqStack: 1
                          4
                          2
         [3, 5]           5
*/

Коли кожне значення перенесено, ми перевіримо, чи воно перевищує максимальне на сьогодні, і збережемо його в кожному кортежі:

maxQ.dequeue(); // pops from dqStack (now empty), so move all from eqStack to dqStack
> 3

// as dequeue moves one value over, it checks if it's greater than the ***previous max*** and stores the max at tuple[1], i.e., [data, max]:
/*
dqStack: [5, 5] => 5 > 4 - update                          eqStack:
         [2, 4] => 2 < 4 - no update                         
         [4, 4] => 4 > 1 - update                            
         [1, 1] => 1st value moved over so max is itself            empty
*/

Оскільки кожне значення переміщується dqStack максимум один раз , ми можемо сказати, що dequeueмає а (а) амортизовану складність часу.


Знаходження максимального значення: O (1) найгірший випадок

Потім, у будь-який момент часу, ми можемо зателефонувати, getMaxщоб отримати поточне максимальне значення в постійному часі O (1). Поки значення MaxQueueне є порожнім, максимальне значення легко витягується з наступного кортежу dqStack.

maxQ.getMax();
> 5

// equivalent to calling peek on the dqStack and pulling out the maximum value:
/*
const peekedTuple = maxQ.dqStack.peek(); // [5, 5]
peekedTuple[1];
> 5
*/


Код

class MaxQueue {
  constructor(...data) {
    // create a dequeue Stack from which we'll pop
    this.dqStack = new Stack();
    // create an enqueue Stack to which we'll push
    this.eqStack = new Stack();
    // if enqueueing data at construction, iterate through data and enqueue each
    if (data.length) for (const datum of data) this.enqueue(datum);
  }
  enqueue(data) { // O(1) constant insertion time
    // if the MaxQueue is empty,
    if (!this.peek()) {
      // push data to the dequeue Stack and indicate it's the max;
      this.dqStack.push([data, data]); // e.g., enqueue(8) ==> [data: 8, max: 8]
    } else {
      // otherwise, the MaxQueue is not empty; push data to enqueue Stack
      this.eqStack.push(data);
      // save a reference to the tuple that's next in line to be dequeued
      const next = this.dqStack.peek();
      // if the enqueueing data is > the max in that tuple, update it
      if (data > next[1]) next[1] = data;
    }
  }
  moveAllFromEqToDq() { // O(1) amortized as each value will move at most once
    // start max at -Infinity for comparison with the first value
    let max = -Infinity;
    // until enqueue Stack is empty,
    while (this.eqStack.peek()) {
      // pop from enqueue Stack and save its data
      const data = this.eqStack.pop();
      // if data is > max, set max to data
      if (data > max) max = data;
      // push to dequeue Stack and indicate the current max; e.g., [data: 7: max: 8]
      this.dqStack.push([data, max]);
    }
  }
  dequeue() { // O(1) amortized deletion due to calling moveAllFromEqToDq from time-to-time
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // pop from the dequeue Stack and save it's data
    const [data] = this.dqStack.pop();
    // if there's no data left in dequeue Stack, move all data from enqueue Stack
    if (!this.dqStack.peek()) this.moveAllFromEqToDq();
    // return the data
    return data;
  }
  peek() { // O(1) constant peek time
    // if the MaxQueue is empty, return undefined
    if (!this.dqStack.peek()) return;
    // peek at dequeue Stack and return its data
    return this.dqStack.peek()[0];
  }
  getMax() { // O(1) constant time to find maximum value
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // peek at dequeue Stack and return the current max
    return this.dqStack.peek()[1];
  }
}


0

Ми знаємо, що натискання та натискання - це постійні операції в часі [O (1), а точніше].

Але коли ми думаємо про get_min () [тобто знайти поточне мінімальне число в черзі], як правило, перше, що спадає на думку, це пошук по всій черзі кожного разу, коли робиться запит на мінімальний елемент. Але це ніколи не дасть постійної роботи в часі, що є головною метою проблеми.

Зазвичай це дуже часто задають на інтерв’ю, тому ви повинні знати фокус

Для цього ми повинні використати ще дві черги, які будуть вести відстеження мінімального елемента, і ми повинні продовжувати модифікувати ці 2 черги, виконуючи операції push та pop у черзі, щоб мінімальний елемент був отриманий за час O (1) .

Ось самоописовий псевдокод, заснований на згаданому вище підході.

    Queue q, minq1, minq2;
    isMinq1Current=true;   
    void push(int a)
    {
      q.push(a);
      if(isMinq1Current)
      {
        if(minq1.empty) minq1.push(a);
        else
        {
          while(!minq1.empty && minq1.top < =a) minq2.push(minq1.pop());
          minq2.push(a);
          while(!minq1.empty) minq1.pop();
          isMinq1Current=false;
        }
      }
      else
      {
        //mirror if(isMinq1Current) branch. 
      }
    }
     
    int pop()
    { 
      int a = q.pop();
      if(isMinq1Current)
      {
        if(a==minq1.top) minq1.pop();
      }
      else
      {
        //mirror if(isMinq1Current) branch.    
      }
    return a;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.