E117. Модификация стека и очереди для извлечения минимума за O (1)

e-maxx algorithm original: C/C++ #algorithm #data-structures #emaxx
Le texte du problème est traduit du russe pour la langue sélectionnée. Le code reste inchangé.

Источник: e-maxx.ru/algo, страница PDF 388.

Здесь мы рассмотрим три задачи: модифицирование стека с добавлением извлечения наименьшего elementа за O (1), аналогичное модифицирование очереди, а также применение их к задаче нахождения минимума во всех подотрезках фиксированной длины данного tableauа за O (N).

Модификация стека

it is required добавить возможность извлечения минимума из стека за O (1), сохранив такой же асимптотику добавления и удаления elementов из стека. Для этого будем хранить в стеке не сами elementы, а пары: element и минимум в стеке, начиная с этого elementа и

ниже. Иными словами, если представить стек как tableau пар, то

stack[i].second = min { stack[j].first }

j = 0..i

Понятно, что тогда нахождение минимума во всём стеке будет заключаться просто во взятии значения stack.top().second. Также очевидно, что при добавлении нового elementа в стек величина second будет равна min (stack.top(). second, new_element). Удаление elementа из стека ничем не отличается от удаления из обычного стека, поскольку удаляемый element никак не мог повлиять на значения second для оставшихся elementов. Implémentation:

stack< pair<int,int> > st;

● Добавление elementа:

int minima = st.empty() ? new_element : min (new_element, st.top().second);

st.push (make_pair (new_element, minima));

● Извлечение elementа:

int result = st.top().first;

st.pop();

● Нахождение минимума:

minima = st.top().second;

Модификация очереди. Способ 1

Здесь рассмотрим простой способ модификации очереди, но имеющий тот недостаток, что модифицированная очередь реально может хранить не все elementы (т.е. при извлечении elementа из очереди нам надо будет знать значение elementа, который мы хотим извлечь). Ясно, что это весьма специфичная ситуация (обычно очередь нужна как раз для того, чтобы узнавать очередной element, а не наоборот), однако этот способ привлекателен своей простотой. Также этот метод применим к задаче о нахождении минимума в подотрезках (см. ниже). Ключевая идея заключается в том, чтобы реально хранить в очереди не все elementы, а только нужные нам для определения минимума. А именно, пусть очередь представляет собой неубывающую последовательность чисел (т.е. в голове хранится наименьшее значение), причём, разумеется, не произвольную, а всегда содержащую минимум. Тогда минимум во всей очереди всегда будет являться первым её elementом. Перед добавлением нового elementа в очередь достаточно произвести "срезку": пока в хвосте очереди находится element, больший нового elementа, будем удалять этот element из очереди; затем добавим новый element в конец очереди. Тем самым мы, с одной стороны, не нарушим порядка, а с другой стороны, не потеряем текущий element, если он на каком-либо последующем шаге окажется минимумом. Но при извлечении elementа из головы очереди его там, вообще говоря, может уже не оказаться - наша модифицированная очередь могла выкинуть этот element в процессе перестроения. Поэтому при удалении elementа нам надо знать значение извлекаемого elementа - если element с этим значением находится в голове очереди, то извлекаем его; иначе просто ничего не делаем. Рассмотрим реализацию вышеописанных операций:

deque<int> q;

● Нахождение минимума:

current_minimum = q.front();

● Добавление elementа:

while (!q.empty() && q.back() > added_element)

q.pop_back();

q.push_back (added_element);

● Извлечение elementа:

if (!q.empty() && q.front() == removed_element)

q.pop_front();

Понятно, что в среднем время выполнения всех этих операций есть O (1).

Модификация очереди. Способ 2

Рассмотрим здесь другой способ модификации очереди для извлечения минимума за O (1), который несколько более сложен для реализации, однако лишён основного недостатка предыдущего метода: все elementы очереди реально сохраняются в ней, и, в частности, при извлечении elementа не it is required знать его значение. Идея заключается в том, чтобы свести задачу к задаче на стеках, которая уже была нами решена. Научимся моделировать очередь с помощью двух стеков. Заведём два стека: s1 и s2; разумеется, имеются в виду стеки, модифицированные для нахождения минимума за O (1). Добавлять новые elementы будет всегда в стек s1, а извлекать elementы - только из стека s2. При этом, если при попытке извлечения elementа из стека s2 он оказался пустым, просто перенесём все elementы из стека s1 в стек s2 (при этом elementы в стеке s2 получатся уже в обратном порядке, что нам и нужно для извлечения elementов; стек s1 же станет пустым). Наконец, нахождение минимума в очереди будет фактически заключаться в нахождении минимума из минимума в стеке s1 и минимума в стеке s2. Тем самым, мы выполняем все операции по-прежнему за O (1) (по той простой причине, что каждый element в худшем случае 1 раз добавляется в стек s1, 1 раз переносится в стек s2 и 1 раз извлекается из стека s2). Implémentation:

stack< pair<int,int> > s1, s2;

● Нахождение минимума:

if (s1.empty() || s2.empty())

current_minimum = s1.empty ? s2.top().second : s1.top().second;

else

current_minimum = min (s1.top().second, s2.top().second);

● Добавление elementа:

int minima = s1.empty() ? new_element : min (new_element, s1.top().second);

s1.push (make_pair (new_element, minima));

● Извлечение elementа:

if (s2.empty())
while (!s1.empty()) {
int element = s1.top().first;

s1.pop();

int minima = s2.empty() ? element : min (element, s2.top

().second);

s2.push (make_pair (element, minima));

}

result = s2.top().first;

s2.pop();

Problème нахождения минимума во всех

подотрезках фиксированной длины данного tableauа

Пусть дан tableau A длины N, и given number M ≤ N. it is required find минимум в каждом подотрезке длины M данного tableauа, т.е. find:

min A[i], min A[i], min A[i], ..., min A[i]

0≤i≤M-1 1≤i≤M 2≤i≤M+1 N-M≤i≤N-1

Решим эту задачу за линейное время, т.е. O (N). Для этого достаточно завести очередь, модифицированную для нахождения минимума за O (1), что было рассмотрено нами выше, причём в данной задаче подойдёт любой из двух методов реализации такой очереди. Далее Solution уже понятно: добавим в очередь первые M elementов tableauа, найдём в ней минимум и выведем его, затем добавим в очередь следующий element, и извлечём из неё первый element tableauа, снова выведем минимум, и т.д. Поскольку все операции с очередью выполняются в среднем за константное время, то и Asymptotic complexity всего Algorithmeа получится O (N). Стоит заметить, что Implémentation модифицированной очереди первым методом проще, однако для неё, вероятно, поit is required хранить весь tableau (поскольку на i-ом шаге поit is required знать i-ый и (i-M)-ый elementы tableauа). При реализации очереди вторым методом tableau A хранить явно не понадобится - только узнавать очередной, i-ый element tableauа.

C# solution

brouillon automatique, à relire avant soumission
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    stack[i].second = min { stack[j].first }
                     j = 0..i
    stack< pair<int,int> > st;
    int minima = st.empty() ? new_element : min (new_element, st.top().second);
    st.push (make_pair (new_element, minima));
    int result = st.top().first;
    st.pop();
    minima = st.top().second;
    deque<int> q;
    current_minimum = q.front();
    while (!q.empty() && q.back() > added_element)
            q.pop_back();
    q.push_back (added_element);
    if (!q.empty() && q.front() == removed_element)
            q.pop_front();
    stack< pair<int,int> > s1, s2;
    if (s1.empty() || s2.empty())
            current_minimum = s1.empty ? s2.top().second : s1.top().second;
    else
            current_minimum = min (s1.top().second, s2.top().second);
    int minima = s1.empty() ? new_element : min (new_element, s1.top().second);
    s1.push (make_pair (new_element, minima));
    if (s2.empty())
            while (!s1.empty()) {
                    int element = s1.top().first;
                    s1.pop();
                    int minima = s2.empty() ? element : min (element, s2.top
    ().second);
                    s2.push (make_pair (element, minima));
            }
    result = s2.top().first;
    s2.pop();
    min A[i],    min A[i],    min A[i],    ...,    min A[i]
    0≤i≤M-1      1≤i≤M        2≤i≤M+1              N-M≤i≤N-1
}

C++ solution

correspondant/original
stack[i].second = min { stack[j].first }
                 j = 0..i
stack< pair<int,int> > st;
int minima = st.empty() ? new_element : min (new_element, st.top().second);
st.push (make_pair (new_element, minima));
int result = st.top().first;
st.pop();
minima = st.top().second;
deque<int> q;
current_minimum = q.front();
while (!q.empty() && q.back() > added_element)
        q.pop_back();
q.push_back (added_element);
if (!q.empty() && q.front() == removed_element)
        q.pop_front();
stack< pair<int,int> > s1, s2;
if (s1.empty() || s2.empty())
        current_minimum = s1.empty ? s2.top().second : s1.top().second;
else
        current_minimum = min (s1.top().second, s2.top().second);
int minima = s1.empty() ? new_element : min (new_element, s1.top().second);
s1.push (make_pair (new_element, minima));
if (s2.empty())
        while (!s1.empty()) {
                int element = s1.top().first;
                s1.pop();
                int minima = s2.empty() ? element : min (element, s2.top
().second);
                s2.push (make_pair (element, minima));
        }
result = s2.top().first;
s2.pop();
min A[i],    min A[i],    min A[i],    ...,    min A[i]
0≤i≤M-1      1≤i≤M        2≤i≤M+1              N-M≤i≤N-1

Java solution

brouillon automatique, à relire avant soumission
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    stack[i].second = min { stack[j].first }
                     j = 0..i
    stack< pair<int,int> > st;
    int minima = st.empty() ? new_element : min (new_element, st.top().second);
    st.push (make_pair (new_element, minima));
    int result = st.top().first;
    st.pop();
    minima = st.top().second;
    deque<int> q;
    current_minimum = q.front();
    while (!q.empty() && q.back() > added_element)
            q.pop_back();
    q.push_back (added_element);
    if (!q.empty() && q.front() == removed_element)
            q.pop_front();
    stack< pair<int,int> > s1, s2;
    if (s1.empty() || s2.empty())
            current_minimum = s1.empty ? s2.top().second : s1.top().second;
    else
            current_minimum = min (s1.top().second, s2.top().second);
    int minima = s1.empty() ? new_element : min (new_element, s1.top().second);
    s1.push (make_pair (new_element, minima));
    if (s2.empty())
            while (!s1.empty()) {
                    int element = s1.top().first;
                    s1.pop();
                    int minima = s2.empty() ? element : min (element, s2.top
    ().second);
                    s2.push (make_pair (element, minima));
            }
    result = s2.top().first;
    s2.pop();
    min A[i],    min A[i],    min A[i],    ...,    min A[i]
    0≤i≤M-1      1≤i≤M        2≤i≤M+1              N-M≤i≤N-1
}

Материал разбит как Algorithmeическая Problème: изучить постановку, понять асимптотику и реализовать Algorithme на выбранном языке.

Vacancies for this task

offres actives with overlapping task tags are affichés.

Toutes les offres
Il n'y a pas encore d'offres actives.