Как найти минимальное значение в стеке

I have a stack which contains some integer data. I want to find out the min value from Stack in O(1) time. Any idea?

PS: There is no ordering (increasing/decreasing) of data in Stack.

Thanks,

Naveen

asked Sep 4, 2009 at 4:26

Naveen's user avatar

NaveenNaveen

4391 gold badge6 silver badges15 bronze badges

3

Use two stacks. One is the data, one is the minimums. When you push onto the data stack, push the new minimum onto the minimums stack (the new minimum is the min of the item you’re pushing and whatever is currently on the top of the minimums stack), and when you pop, pop off of both stacks (so that the two stacks always have the same number of elements). To find the minimum element, just look at the top of the minimums stack.

Pushing, popping and finding the min value are O(1).

answered Sep 4, 2009 at 4:41

ESRogs's user avatar

ESRogsESRogs

2,3012 gold badges16 silver badges11 bronze badges

24

O(n) is the best you’re gonna do — you’d have to check each one of the values and compare them to the aggregator minimum, otherwise how would you know you got the lowest?

If you want, you can store the minimum as the values are added, making the pushes more expensive for the benefit of an O(1) read (of the pre-calculated minimum), but that’s it.

answered Sep 4, 2009 at 4:30

Jason Kleban's user avatar

Jason KlebanJason Kleban

19.8k17 gold badges72 silver badges125 bronze badges

4

A stack by definition is push/pop (LIFO) data structure. You can’t using a single stack!

letsc's user avatar

letsc

2,0855 gold badges24 silver badges43 bronze badges

answered Sep 4, 2009 at 4:28

Khaled Alshaya's user avatar

Khaled AlshayaKhaled Alshaya

93.8k39 gold badges175 silver badges233 bronze badges

2

I am not sure why you expect to do this in constant time for arbitrary length. The best you will be able to do is O(n)

answered Sep 4, 2009 at 4:31

hhafez's user avatar

hhafezhhafez

38.7k38 gold badges112 silver badges143 bronze badges

4

You’ll probably want some kind of priority heap if you want to always pop the least element. If you want to pop what was last pushed, but be able to know the order of the elements remaining in the stack, some kind of search tree e.g. red-black will support deletion of an element from an arbitrary position (your stack would have a pointer to the tree node so when you pop you can find it).

If you only need to know the minimum (or max) remaining in the stack then ESRogs’ is optimal.

answered Sep 4, 2009 at 4:45

Jonathan Graehl's user avatar

Here is the Python implementation of ESRogs algorithm using lists as stacks:

class ConstantStack:
    def __init__(self):
        self.stack = []
        self.min_stack = []
    def push(self,item):
        self.stack.append(item)
        if len(self.min_stack) == 0:
            self.min_stack.append(item)
            return
        # Get the smaller item between the pushed item and the top of the stack
        smallest = min(item,self.min_stack[-1])
        self.min_stack.append(smallest)
    def pop(self):
        self.min_stack.pop()
        return self.stack.pop()
    def min(self):
        # NOTE: min_stack[-1] is equivalent to peek()
        return self.min_stack[-1]

Here is an example of its usage:

>>> s = ConstantStack()
>>> s.push(3)
>>> s.push(7)
>>> s.push(6)
>>> s.push(1)
>>> s.min()
1
>>> s.pop()
1
>>> # Now that 1 is gone, 3 is the next smallest
>>> s.min()
3
>>> s.pop()
6
>>> # 6 was popped but 3 still remains the smallest
>>> s.min()
3
>>> s.pop()
7
>>> s.min()
3
>>> s.pop()
3

answered Dec 3, 2009 at 6:10

jkupferman's user avatar

jkupfermanjkupferman

6104 silver badges7 bronze badges

#define STACKSIZE 50

typedef struct stack
{
    int item[STACKSIZE];
    int top;
}MULSTACKEX;

void InitStack(MULSTACKEX &st)
{
   st.item[STACKSIZE] = 0;
   st.top = -1;
}

void Push(MULSTACKEX &st1, MULSTACKEX &st2, int elem)
{
    if(st1.top == -1)
    {   
        st1.top++;
        st1.item[st1.top] = elem;

        st2.top++;
        st2.item[st2.top] = elem;
    }
    else
    {
        st1.top++;
        st1.item[st1.top] = elem;

        if(elem < st2.item[st2.top])
        {
            st2.top++;
            st2.item[st2.top] = elem;
        }
    }
}

void Display(MULSTACKEX &st1, MULSTACKEX &st2)
{
    cout<<"stack1 elements: "<<endl;
    for(int i = 0; i <= st1.top; i++)
    {
        cout<<st1.item[i]<<"->";
    }

    cout<<endl;
    cout<<"stack2 elements: "<<endl;
    for(int i = 0; i <= st2.top; i++)
    {
        cout<<st2.item[i]<<"->";
    }

}

int Pop(MULSTACKEX &st1, MULSTACKEX &st2)
{
    int elem = 0;
    if(st1.item[st1.top] == st2.item[st2.top])
    {
        elem = st2.item[st2.top];
        st2.top--;

        elem = st1.item[st1.top];
        st1.top--;
    }
    else
    {
        elem = st1.item[st1.top];
        st1.top--;
    }

    return elem;    
}
int FindMin(MULSTACKEX &st2)
{
    int elem = st2.item[st2.top];
    return elem;
}

int _tmain(int argc, _TCHAR* argv[])
{
    MULSTACKEX stack1, stack2;

    InitStack(stack1); 
    InitStack(stack2);


    Push(stack1,stack2,13); 
    Push(stack1,stack2,17);
    Push(stack1,stack2,5);
    Display(stack1,stack2);

    int min_elem1 = FindMin(stack2);
    cout<<"Min element in the list is: "<<min_elem1<<endl<<endl;

    int deletedelem2 = Pop(stack1,stack2);
    cout<<"Pop element from the stack:"<< deletedelem2 <<endl<<endl;
    Display(stack1,stack2);

    cout<<endl<<endl;

    Push(stack1,stack2,19);
    Push(stack1,stack2,8);
    Display(stack1,stack2);

    cout<<endl<<endl;

    int deletedelem1 = Pop(stack1,stack2);
    cout<<"Pop element from the stack:"<< deletedelem1 <<endl<<endl;
    Display(stack1,stack2);

    int min_elem2 = FindMin(stack2);
    cout<<"Min element in the list is: "<<min_elem2<<endl<<endl;

    return 0;
}

brainimus's user avatar

brainimus

10.5k12 gold badges41 silver badges64 bronze badges

answered Dec 16, 2009 at 8:50

Shyam Raja's user avatar

Instead of pushing 1 value, we push a pair of numbers. first element will be element that you want to push, second element would be minimum element of the stack and we should keep track of the minimum element. (element,minOfStack)

let say you have an empty array. when first you push data to the stack,firs element is also minumum value.

 data=[(1,1)]

then you add value 2. you check the minimim value of the stack which is 1, 1<2, so you push (2,1) to the array

data=[(1,1),(2,1)]

Here is the python implementation

class StackTrackingMin:
    def __init__(self):
        self._data=[]
    def push(self,x:int)->None:
        currentMin=self.getMin()
        if currentMin==None or currentMin>x:
            currentMin=x
        self._data.append((x,currentMin))
    def pop(self)->None:
        self._data.pop() # pop does not return anything
    def top(self)->int:
        return self._date[-1] if self._data else None
    def getMin(self)->int:
        return self._data[-1][1] if self._data else None

answered Dec 20, 2020 at 18:09

Yilmaz's user avatar

YilmazYilmaz

31.2k10 gold badges147 silver badges181 bronze badges

Ryokka

0 / 0 / 0

Регистрация: 30.05.2019

Сообщений: 6

1

Найти минимальный и максимальный элементы стека

30.05.2019, 14:02. Показов 15743. Ответов 12

Метки нет (Все метки)


Студворк — интернет-сервис помощи студентам

Найти максимальный и минимальный элементы стека.

Вот код, который выводит количество, первое и последнее число, осталось найти мин и макс

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <stack>
#include <vector>
#include<deque>
using namespace std;
 
int main()
{
    stack<int>  stkd;
     for(int i=0;i<10;++i)
     {
         int temp=rand()%10;
         cout<<temp<< " ";
         stkd.push(temp);
        }
 
     cout<<endl;
    cout<< " size "<< stkd.size()<<endl;
     cout<<"top "<< stkd.top()<<endl;
 
   for(int i=0;i<9;++i)
   {
        stkd.pop();
   }
    cout<<"bottom "<<stkd.top()<<endl;
 
 
 
    cout << "Hello world!" << endl;
    return 0;
}



0



Programming

Эксперт

94731 / 64177 / 26122

Регистрация: 12.04.2006

Сообщений: 116,782

30.05.2019, 14:02

12

Модератор

Эксперт С++

13104 / 10376 / 6207

Регистрация: 18.12.2011

Сообщений: 27,754

30.05.2019, 14:10

2



0



0 / 0 / 0

Регистрация: 30.05.2019

Сообщений: 6

30.05.2019, 14:26

 [ТС]

3

Есть возможность изменить мой код? Пытался писать, ничего не выходит, простит std::stack



0



oleg-m1973

6577 / 4562 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

30.05.2019, 14:40

4

Цитата
Сообщение от Ryokka
Посмотреть сообщение

Вот код, который выводит количество, первое и последнее число, осталось найти мин и макс

У std::stack есть базовый контейнер, в данном случае std::deque, доступ к нему можно получить через std::stack::c

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   cout<< " size "<< stkd.size()<<endl;
     cout<<"top "<< stkd.c.front()<<endl;
    cout<<"bottom "<<stkd.c.back() <<endl;
 
auto it = stkd.c.begin();
int min = *it;
int max = *it;
++it;
for (auto end = stkd.c.end(); it  = end; ++it)
{
   if (min < *it)
     min = *it;
   else if (max > *it)
     max = *it;
}
 
     cout<<"min "<< max<<endl;
    cout<<"min "<<min <<endl;

Добавлено через 1 минуту
Вернее соврал, нельзя, он protected



0



Kuzia domovenok

4023 / 3280 / 920

Регистрация: 25.03.2012

Сообщений: 12,263

Записей в блоге: 1

30.05.2019, 14:40

5

Лучший ответ Сообщение было отмечено Ryokka как решение

Решение

Цитата
Сообщение от Ryokka
Посмотреть сообщение

возможность изменить мой код?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <stack>
#include <vector>
#include<deque>
using namespace std;
 
int main()
{
    stack<int>  stkd, stk2;
    for (int i = 0; i < 10; ++i)
    {
        int temp = rand() % 10;
        cout << temp << " ";
        stkd.push(temp);
    }
    int max= stkd.top(), min= stkd.top();
    while (!stkd.empty())
    {
        int temp = stkd.top();
        stkd.pop();
 
        stk2.push(temp);
        if (temp > max)
            max = temp;
        else if (temp < min)
            min = temp;
    }
    stkd = stk2;
    cout << endl << "max=" << max << ", min=" << min << endl;
}



0



6577 / 4562 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

30.05.2019, 14:41

6

Лучше используй вместо stack<int> — deque<int>



0



4023 / 3280 / 920

Регистрация: 25.03.2012

Сообщений: 12,263

Записей в блоге: 1

30.05.2019, 14:55

7

oleg-m1973, а ещё лучше максимум в очереди с приоритетом искать или в куче… вообще зашибись бы было,
но увы преподы часто школоте составляют задания по шаблону

«в контейнере <массив/список/стек/очередь/дерево…> необходимо <найти максимум/минимум/удалить элемент/вставить элемент> при этом элементы это структуры с полями <train/car/aeroflot…>»

причём алгоритмы и структуры данных подбираются в рандомных комбинациях, зачастую комбинации не соответствуют никакой логике — строятся по принципу «шоб було» больше вариантов заданий.



0



6577 / 4562 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

30.05.2019, 15:49

8

Цитата
Сообщение от Kuzia domovenok
Посмотреть сообщение

но увы преподы часто школоте составляют задания по шаблону
«в контейнере <массив/список/стек/очередь/дерево…>

std::stack это ни разу не контейнер



0



4023 / 3280 / 920

Регистрация: 25.03.2012

Сообщений: 12,263

Записей в блоге: 1

30.05.2019, 16:02

9

oleg-m1973, в контексте коммента это на что-то меняет? А если не стд-стек? Ты по сути коммента хотел что-то ответить?



0



6577 / 4562 / 1843

Регистрация: 07.05.2019

Сообщений: 13,726

30.05.2019, 16:05

10

Цитата
Сообщение от Kuzia domovenok
Посмотреть сообщение

oleg-m1973, в контексте коммента это на что-то меняет? А если не стд-стек? Ты по сути коммента хотел что-то ответить?

А какая в нём суть? Ты знаешь какое именно было задание, что там именно std::stack?



0



7427 / 5021 / 2891

Регистрация: 18.12.2017

Сообщений: 15,694

30.05.2019, 16:13

11

Kuzia domovenok, хотел спросить дополнительный стек не обязателен, но нужен для восстановления начального стека, так ? но разве элементы восстановятся не наоборот, т.е. реверсом ?



0



Kuzia domovenok

4023 / 3280 / 920

Регистрация: 25.03.2012

Сообщений: 12,263

Записей в блоге: 1

30.05.2019, 16:20

12

Yetty, я вообще в последний момент вспомнил даже о том, что там ещё один стек надо восстанавливать. Я вообще думал можно и пустым его оставить, но если очень хочется.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <stack>
#include <vector>
#include<deque>
using namespace std;
 
int main()
{
    stack<int>  stkd, stk2;
    for (int i = 0; i < 10; ++i)
    {
        int temp = rand() % 10;
        cout << temp << " ";
        stkd.push(temp);
    }
    stk2 = stkd;
    int max= stk2.top(), min= stk2.top();
    while (!stk2.empty())
    {
        int temp = stk2.top();
        stk2.pop(); 
        if (temp > max)
            max = temp;
        else if (temp < min)
            min = temp;
    }
    cout << endl << "max=" << max << ", min=" << min << endl;
}



0



7427 / 5021 / 2891

Регистрация: 18.12.2017

Сообщений: 15,694

30.05.2019, 16:48

13

Ryokka, задача заключается в поиске max и min в уже сформированном стеке ? не подойдёт находить сразу при формировании стека ?



0



Итак, оценка времени работы функция push, pop и min — O(1).

Экстремумы изменяются не часто. Фактически минимум может поменяться только при добавлении нового элемента.

Одно из решений — сравнивать добавляемые элементы с минимальным значением. Когда минимальное значение (minValue) удаляется из стека, приходится «перерывать» весь стек в поисках нового минимума. К сожалению, это нарушает ограничение на время выполнения О(1).

Если мы будем отслеживать минимум в каждом состоянии, то легко узнаем минимальный элемент. Можно, например, записывать для каждого узла текущий минимальный элемент, Затем, чтобы найти min, достаточно «вытолкнуть» вершину и посмотреть, какой элемент является минимальным.

Как только элемент помещается в стек, локальное значение минимума становится глобальным.

public class StackWithMin extends Stack<NodeWithMin> {
    public void push(int value) {
        int newMin = Math.min(value,min());
        super.push(new NodeWithMin(value, newMin));
    }

    public int min() {
        if (this.isEmpty()) {
            return Integer.MAX_VALUE;
        } else {
            return peek().min;
          }
    }
}

class NodeWithMin {
    public int value;
    public int min;
    public NodeWithMin(int v, int min) {
        value = v;
        this.min = min;
    }
}

У решения один недостаток — если нужно обрабатывать огромный стек, то отслеживание минимального элемента потребует много ресурсов. Существует ли лучшее решение?

Оптимизировать код можно за счет использования дополнительного стека, который будет отслеживать минимумы.

public class StackWithMin2 extends Stack<Integer> {
    Stack<Integer> s2;
    public StackWithMin2() {
        s2 = new Stack<Integer>();
    }

    public void push(int value) {
        if (value <= min()) {
            s2.push(value);
        }
        super.push(value);
    }

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

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

Почему такое решение более эффективно? Предположим, что мы работаем с огромным стеком, первый вставленный элемент автоматически станет минимумом. В первом решение необходимо хранить n чисел, где n — размер стека. Во втором решении достаточно сохранить несколько фрагментов данных.

Разбор взят из перевода книги Г. Лакман Макдауэлл и предназначен исключительно для ознакомления.
Если он вам понравился, то рекомендуем купить книгу «Карьера программиста. Как устроиться на работу в Google, Microsoft или другую ведущую IT-компанию».

Дизайн stack для поддержки дополнительной операции, которая возвращает минимальный элемент из stack за постоянное время. Стек должен продолжать поддерживать все остальные операции, такие как push, pop, top, size, empty и т. д., без снижения производительности этих операций.

Потренируйтесь в этой проблеме

Первое решение, которое приходит на ум, — иметь переменную-член в stack для отслеживания минимального числа. К сожалению, это не сработает, поскольку мы не можем получить следующее минимальное число после извлечения минимального.

 
Правильный подход использует два stack — основной стек для хранения фактических элементов stack и вспомогательный стек для хранения требуемых элементов, необходимых для определения минимального количества за постоянное время. Эта реализация требует нескольких изменений в операциях push и pop:

1. Толкающая операция

Идея состоит в том, чтобы поместить новый элемент в основной стек и поместить его во вспомогательный стек только в том случае, если стек пуст или новый элемент меньше или равен текущему верхнему элементу вспомогательного stack.

2. Поп-операция

Для операции извлечения удалите верхний элемент из основного stack и удалите его из вспомогательного stack, только если он равен текущему минимальному элементу, т. е. верхний элемент как основного, так и вспомогательного stack один и тот же. После извлечения минимального числа следующее минимальное число появляется на вершине вспомогательного stack.

3. Минимальная работа

Вершина вспомогательного stack всегда возвращает минимальное число, поскольку мы помещаем минимальное число во вспомогательный стек и удаляем минимальное число из вспомогательного stack, только если оно удаляется из основного stack.

 
В следующей таблице показаны описанные выше операции:

 
Stack Operations

 
Реализацию можно увидеть ниже на C++, Java и Python:

C++

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

#include <iostream>

#include <stack>

using namespace std;

class MinStack

{

    stack<int> s;       // основной stack для хранения элементов

    stack<int> aux;     // вспомогательный stack для хранения минимума элементов

public:

    // Вставляет заданный элемент на вершину stack

    void push(int val)

    {

        // поместить заданный элемент в основной stack

        s.push(val);

        // если вспомогательный stack пуст, запихиваем в него данный элемент

        if (aux.empty()) {

            aux.push(val);

        }

        else {

            // поместить заданный элемент во вспомогательный stack

            // если он меньше или равен текущему минимуму

            if (aux.top() >= val) {

                aux.push(val);

            }

        }

    }

    // Удаляет верхний элемент из stack и возвращает его

    int pop()

    {

        // удаляем верхний элемент из основного stack

        int top = s.top();

        s.pop();

        // удаляем верхний элемент из вспомогательного stack, только если он минимальный

        if (top == aux.top()) {

            aux.pop();

        }

        // вернуть удаленный элемент

        return top;

    }

    // Возвращает верхний элемент stack

    int top() {

        return s.top();

    }

    // Возвращает общее количество элементов в stack

    int size() {

        return s.size();

    }

    // Возвращает true, если stack пуст; ложь в противном случае

    bool isEmpty() {

        return s.empty();

    }

    // Возвращает минимальный элемент из stack за постоянное время

    int getMin()

    {

        return aux.top();

    }

};

int main()

{

    MinStack s;

    s.push(6);

    cout << s.getMin() << endl;    // печатает 6

    s.push(7);

    cout << s.getMin() << endl;    // печатает 6

    s.push(8);

    cout << s.getMin() << endl;    // печатает 6

    s.push(5);

    cout << s.getMin() << endl;    // печатает 5

    s.push(3);

    cout << s.getMin() << endl;    // печатает 3

    cout << s.pop() << endl;    // печатает 3

    cout << s.getMin() << endl;    // печатает 5

    s.push(10);

    cout << s.getMin() << endl;    // печатает 5

    cout << s.pop() << endl;    // печатает 10

    cout << s.getMin() << endl;    // печатает 5

    cout << s.pop() << endl;    // печатает 5

    cout << s.getMin() << endl;    // печатает 6

    return 0;

}

Скачать  Выполнить код

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

import java.util.Stack;

class MinStack

{

    private Stack<Integer> s;       // основной stack для хранения элементов

    private Stack<Integer> aux;     // вспомогательный stack для хранения минимума элементов

    // Конструктор

    public MinStack()

    {

        s = new Stack<>();

        aux = new Stack<>();

    }

    // Вставляет заданный элемент на вершину stack

    public void push(int val)

    {

        // поместить заданный элемент в основной stack

        s.push(val);

        // если вспомогательный stack пуст, запихиваем в него данный элемент

        if (aux.isEmpty()) {

            aux.push(val);

        }

        else {

            // поместить заданный элемент во вспомогательный stack

            // если он меньше или равен текущему минимуму

            if (aux.peek() >= val) {

                aux.push(val);

            }

        }

    }

    // Удаляет верхний элемент из stack и возвращает его

    public int pop()

    {

        if (isEmpty())

        {

            System.out.println(«Stack underflow!!»);

            System.exit(1);

        }

        // удаляем верхний элемент из основного stack

        int top = s.pop();

        // удаляем верхний элемент из вспомогательного stack

        // только если она минимальна

        if (top == aux.peek()) {

            aux.pop();

        }

        // вернуть удаленный элемент

        return top;

    }

    // Возвращает верхний элемент stack

    public int top() {

        return s.peek();

    }

    // Возвращает общее количество элементов в stack

    public int size() {

        return s.size();

    }

    // Возвращает true, если stack пуст; ложь в противном случае

    public boolean isEmpty() {

        return s.isEmpty();

    }

    // Возвращает минимальный элемент из stack за постоянное время

    public int getMin()

    {

        if (aux.isEmpty())

        {

            System.out.println(«Stack underflow!!»);

            System.exit(1);

        }

        return aux.peek();

    }

}

class Main

{

    public static void main (String[] args)

    {

        MinStack s = new MinStack();

        s.push(6);

        System.out.println(s.getMin());    // печатает 6

        s.push(7);

        System.out.println(s.getMin());    // печатает 6

        s.push(8);

        System.out.println(s.getMin());    // печатает 6

        s.push(5);

        System.out.println(s.getMin());    // печатает 5

        s.push(3);

        System.out.println(s.getMin());    // печатает 3

        System.out.println(s.pop());    // печатает 3

        System.out.println(s.getMin());    // печатает 5

        s.push(10);

        System.out.println(s.getMin());    // печатает 5

        System.out.println(s.pop());    // печатает 10

        System.out.println(s.getMin());    // печатает 5

        System.out.println(s.pop());    // печатает 5

        System.out.println(s.getMin());    // печатает 6

    }

}

Скачать  Выполнить код

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

from collections import deque

class MinStack:

    # 1ТП4Т конструктор

    def __init__(self):

        # Основной stack # для хранения элементов

        self.s = deque()

        # Вспомогательный stack # для хранения минимума элементов

        self.aux = deque()

    # Вставляет заданный элемент поверх stack

    def push(self, val):

        # помещает данный элемент в основной stack

        self.s.append(val)

        #, если вспомогательный stack пуст, поместить в него данный элемент

        if not self.aux:

            self.aux.append(val)

        else:

            # помещает заданный элемент во вспомогательный stack

            #, если он меньше или равен текущему минимуму

            if self.aux[1] >= val:

                self.aux.append(val)

    # Удаляет верхний элемент из stack и возвращает его

    def pop(self):

        if self.isEmpty():

            print(‘Stack underflow’)

            exit(1)

        # удалить верхний элемент из основного stack

        top = self.s.pop()

        # удалить верхний элемент из вспомогательного stack

        # только если он минимальный

        if top == self.aux[1]:

            self.aux.pop()

        # возвращает удаленный элемент

        return top

    # Возвращает верхний элемент stack

    def top(self):

        return self.s[1]

    # Возвращает общее количество элементов в stack.

    def size(self):

        return len(self.s)

    # Возвращает true, если stack пуст; ложь в противном случае

    def isEmpty(self):

        return not self.s

    # Возвращает минимальный элемент из stack за постоянное время.

    def getMin(self):

        if not self.aux:

            print(‘Stack underflow’)

            exit(1)

        return self.aux[1]

if __name__ == ‘__main__’:

    s = MinStack()

    s.push(6)

    print(s.getMin())        # печатает 6

    s.push(7)

    print(s.getMin())        # печатает 6

    s.push(8)

    print(s.getMin())        # печатает 6

    s.push(5)

    print(s.getMin())        # печатает 5

    s.push(3)

    print(s.getMin())        # печатает 3

    print(s.pop())          # печатает 3

    print(s.getMin())        # печатает 5

    s.push(10)

    print(s.getMin())        # печатает 5

    print(s.pop())          # печатает 10

    print(s.getMin())        # печатает 5

    print(s.pop())          # печатает 5

    print(s.getMin())        # печатает 6

Скачать  Выполнить код

 
Продолжить чтение:

Создайте стек, который возвращает минимальный элемент без использования вспомогательного stack

Спасибо за чтение.

Пожалуйста, используйте наш онлайн-компилятор размещать код в комментариях, используя C, C++, Java, Python, JavaScript, C#, PHP и многие другие популярные языки программирования.

Как мы? Порекомендуйте нас своим друзьям и помогите нам расти. Удачного кодирования :)

Стек рекордов (стек минимумов / максимумов)

TL;DR: Стек минимумов (максимумов) — это элементы массива, правее которых до текущего индекса $i$ нет меньших (бóльших) элементов. Часто используется вместо деревьев отрезков и других более сложных структур данных в задачах, где необходимо понимать структуру массива относительно запросов поиска минимума или максимума на отрезке. Строится последовательно для индексов по возрастанию: чтобы из стека минимумов (максимумов) для индекса $i-1$ получить стек минимумов (максимумов) для индекса $i$, нужно удалить из стека все индексы, значения которых не меньше (не больше) $a_i$, а затем добавить индекс $i$ в конец стека. Такое построение суммарно занимает $O(n)$ времени. Для большего понимания обратитесь к картинке и коду ниже. На английском языке стек рекордов известен под названием «monotonic stack».

Стек рекордов или стек минимумов или максимумов (не путать со стеком с минимумом) — очень простая, но при этом очень мощная структура данных, которая во многих задачах может заменить такие более сложные структуры данных как дерево отрезков. При этом в силу ее простоты, отдельное время во время изучения алгоритмов ей не уделяется, поэтому в интернете сложно найти какие-либо материалы по этой теме.

Пример задачи

Рассмотрим стандартный пример задачи, в которой можно применить стек рекордов.

Задача:
Дан массив целых чисел $a$ длины $n$. Необходимо найти сумму минимумов по всем его подотрезкам или другими словами $sum_{l =0}^{n-1} sum_{r = l}^{n-1} min(a_l, a_{l + 1}, ldots, a_r)$.

Очевидным решением за $O(n^3)$ будет перебрать все отрезки и для каждого из них найти минимум. Если зафиксировать левую границу отрезка и перебирать правую по возрастанию, можно поддерживать минимум на лету. Такое решение будет работать за $O(n^2)$. Однако, чтобы продолжить улучшать асимптотику, необходимо сменить перспективу.

Заметим, что минимум на любом отрезке равен какому-то конкретному элементу на этом отрезке (а именно, минимуму). Для простоты пока предположим, что все элементы массива различны. Тогда зафиксируем для каждого отрезка такой элемент. Нам необходимо найти сумму всех таких чисел. Давайте поменяем точку зрения: вместо того чтобы для каждого отрезка рассматривать минимум, мы для каждого элемента $a_i$ массива посчитаем значение $cnt_i$: для скольки отрезков он является минимумом. Тогда ответом на задачу будет $sum_{i=0}^{n-1} a_i cdot cnt_i$. Нам остается лишь найти эти значения $cnt_i$. Но в каком случае $a_i$ является минимумом на каком-то отрезке $[l, r]$? Во-первых, элемент $a_i$ должен лежать на этом отрезке, а во-вторых, все остальные элементы на отрезке должны быть больше $a_i$. В частности, все элементы на отрезке $[l, i — 1]$ больше $a_i$ и все элементы на отрезке $[i + 1, r]$ больше $a_i$. И если это так, то любой такой отрезок $[l, r]$ нам подходит. Тогда предположим, что $l_0$ — это ближайший индекс слева от $i$, такой что $a_{l_0} < a_i$, и $r_0$ — ближайший справа от $i$ такой индекс. Тогда очевидно, что $l$ должно быть больше $l_0$, потому что иначе отрезок $[l, i + 1]$ будет содержать $l_0$, а также $r$ должно быть меньше $r_0$, потому что иначе отрезок $[i + 1, r]$ будет содержать $r_0$.
При этом если $l_0 < l le i le r < r_0$, то любой такой отрезок нам подходит, ведь он содержит индекс $i$ и при этом не содержит никаких элементов, меньших $a_i$. Существует $i — l_0$ подходящих левых границ и $r_0 — i$ подходящих правых границ, и нам подходит любая такая пара, так что $cnt_i = (i — l_0) cdot (r_0 — i)$. Остается лишь для каждого индекса $i$ найти ближайшие слева и справа элементы, которые меньше $a_i$.
В этот момент вам, возможно, хочется сказать «наверное, это как-то делается через дерево отрезков», но не стоит попадать в эту ловушку! Стек минимумов — это как раз таки ровно структура данных, которая позволяет для каждого элемента массива найти ближайший меньший элемент всего в несколько строчек кода без использования каких-либо сложных структур данных типа дерева отрезков.

Но перед тем как мы перейдем непосредственно к рассмотрению стека минимумов, сначала мы должны исправить пару неаккуратностей, которые мы допустили в алгоритме выше. Во-первых, что делать, если таких индексов $l_0$ или $r_0$ не существует? Что, если нет ни одного элемента левее $a_i$, который был бы его меньше? В таком случае в качестве левой границы мы можем выбрать любой индекс от $0$ до $i$. Иными словами, можно считать, что $l_0 = -1$. Аналогично для правой границы можно считать, что $r_0 = n$. Это можно понимать немного иначе, представив, что в массиве есть дополнительные элементы $a_{-1}$ и $a_n$, которые равны $-infty$, и поэтому у каждого элемента массива всегда есть элементы слева и справа, которые меньше.
Вторая проблема — это то, что мы считали, что все элементы массива различны. Если в массиве есть одинаковые элементы, то на отрезках может быть несколько элементов, равных минимуму, и нам нужно точно определить, какой из них мы выберем, чтобы случайно не посчитать один и тот же отрезок несколько раз. Не умаляя общности, можно считать, что мы всегда выбираем самый правый среди минимумов на отрезке (можно было выбрать самый левый; главное — зафиксировать выбор и следовать ему). Это значит, что все элементы на отрезке правее него строго меньше его, а все элементы на отрезке левее него меньше или равны ему. То есть на самом деле нам надо найти не ближайшие слева и справа элементы, меньшие данного, а слева найти ближайший меньший, а справа ближайший не больший. Кардинально это никак алгоритм не поменяет. Альтернативный способ избавиться от проблемы равных чисел — это представить, что массив состоит не из элементов $a_i$, а из пар $(a_i, i)$, которые сравниваются лексикографически (сначала по первому элементу, а при равенстве первого элемента, по второму). В таком случае все элементы массива точно будут различны, так что минимум на любом отрезке будет единственен. На самом деле это в точности соответствует нашему предыдущему способу, если бы среди равных чисел мы выбирали самое левое, потому что если на отрезке есть несколько элементов с одинаковым значением, то меньшим среди них будет элемент с минимальным индексом.

Определение, свойства и построение стека минимумов

Теперь перейдем непосредственно к стеку минимумов, и как он может помочь нам найти для каждого индекса массива ближайший слева меньший элемент. Для начала определим, что такое стек минимумов.

Определение:
Стек минимумов (стек рекордов) для индекса $i$ массива $a$ — это возрастающая последовательность индексов массива $a$, такая что индекс $j le i$ находится в этой последовательности тогда и только тогда, когда $a_j$ строго меньше всех остальных элементов на отрезке $[j, i]$. Если же вместо строго меньше в предыдущем предложении написать строго больше, то такая последовательность называется стеком максимумов.

В частности, последним элементом стека минимумов для индекса $i$ всегда является индекс $i$, потому что $a_i$ больше всех остальных элементов на отрезке $[i, i]$, потому что это единственный элемент на этом отрезке. Первым же элементом в стеке минимумов индекса $i$ является индекс минимума на отрезке $[0, i]$ (самый правый, если есть несколько элементов, равных минимуму), потому что он удовлетворяет условию на вхождение в стек минимумов, но никакой элемент левее него не может удовлетворять этому условию, иначе он был бы меньше минимума. В зависимости от контекста для удобства иногда считают, что первым элементом стека минимумов является $-1$ (подразумевая, что в начале массива есть элемент $a_{-1} = -infty$).

Почему же эта структура называется стеком рекордов? Если мы встанем в точку $i$ и пойдем налево, то элементы стека минимумов будут в точности «рекордными» значениями, которые меньше всего, что мы видели до этого. В частности, из этого легко сделать вывод, что не только индексы в стеке минимумов строго возрастают, но и соответствующие им значения в массиве $a$. Также это значит, что если $j$ и $k$ — последовательные элементы стека рекордов индекса $i$, то элемент $a_k$ является минимумом (самым правым минимумом, если в массиве могут быть одинаковые элементы) на отрезках с правой границей $i$ тогда и только тогда, когда левая граница такого отрезка $l$ строго больше $j$ и не больше $k$ ($j < l le k$).
Эта идея является ключевой в одном из главных алгоритмов, использующем стек рекордов, — вариации алгоритма Тарьяна для поиска минимума на отрезке в оффлайне. Тем самым весь массив до индекса $i$ имеет очень конкретную структуру относительно элементов стека минимумов: элементы стека минимумов — это строго возрастающая последовательность элементов, а все элементы между двумя последовательными элементами стека минимумов не меньше их обоих. Если нарисовать элементы массива как точки $(i, a_i)$ на плоскости, то мы также сможем получить и красивую визуальную геометрическую интуицию:

test

Здесь красные точки — это элементы стека минимумов, а черные — все остальные. Обратите внимание, что все остальные элементы между двумя элементами стека минимумов не меньше их обоих, поэтому все элементы лежат внутри синих стаканов.

Кроме того, если мы обозначим стек минимумов для индекса $i$ как последовательность $j_0$, $j_1$, $ldots$, $j_m$, то, как мы уже поняли, $j_0 = -1$ и $j_m = i$, но кроме того, для любого индекса $j_k$ в стеке минимумов, $j_{k-1}$ является для него тем самым ближайшим слева меньшим элементом (это становится очевидно, если вспомнить процесс, когда мы встаем в индекс $i$ и идем налево в поиске «рекордов»). И эта идея как раз таки позволяет нам найти то, что мы хотели, — ближайший слева от $i$ индекс $l$, такой что $a_l < a_i$, ведь если у нас уже есть стек минимумов, то этот индекс $l$ — это просто предпоследний элемент стека минимумов $j_{m-1}$.

Мы обсудили основные свойства стека минимумов, и, надеюсь, теперь вы согласны, что он имеет фундаментальное значение для понимания структуры массива. В частности, он поможет нам решить задачу поиска суммы минимумов по всем подотрезкам массива. Последний шаг, который нам остается, — это, собственно, найти стеки минимумов для всех индексов массива. Обратите внимание, что сохранить их все у нас вряд ли получится, потому что, к примеру, если массив $a$ возрастающий, то стек минимумов для индекса $i$ содержит все индексы от $-1$ до $i$, поэтому суммарный размер всех стеков минимумов будет квадратичным относительно размера массива. Мы же сделаем нечто немного другое: мы будем перебирать индексы массива $a$ по возрастанию и преобразовывать стек минимумов для индекса $i-1$ в стек минимумов для индекса $i$ шаг за шагом. Таким образом, в момент, когда мы находимся в индексе $i$, перед нами стек минимумов для индекса $i$, который мы можем использовать.

Как же поменяется стек минимумов, если мы перейдем от индекса $i-1$ к индексу $i$? Вспомним определение: стек минимумов — это последовательность всех индексов $j$, таких что $a_j$ меньше всех других элементов на отрезке от $j$ до $i-1$. Если какой-то элемент $a_j$ не был меньше всех элементов правее него, то при добавлении элемента $a_{i}$ он таковым не станет. А если элемент $j$ был в стеке минимумов, то он уже точно меньше всех элементов от $j$ до $i-1$, так что он должен остаться в стеке минимумов тогда и только тогда, когда он меньше $a_{i}$. Так как мы уже знаем, что значения в массиве $a$ элементов стека минимумов возрастают, то все индексы $j$, которые не удовлетворяют этому условию, находятся на конце стека. То есть алгоритм пересчета стека минимумов следующий: мы удаляем индексы $j$ с вершины стека, пока $a_j ge a_{i}$, после чего, когда такие элементы закончились, добавляем индекс $i$ на вершину стека. И это все! На самом деле все очень просто. Надеюсь, теперь стало понятно, почему такую последовательность мы назвали стеком. Для большего понимания алгоритма рекомендую посмотреть на код:

// a -- array of length n
vector<int> stack_of_min;
for (int i = 0; i < n; i++) {
    while (!stack_of_min.empty() && a[stack_of_min.back()] >= a[i]) {
        stack_of_min.pop_back();
    }
    stack_of_min.push_back(i);
    // use stack_of_min as a stack of minimums for index i
}

Такой алгоритм работает за линейное время, потому что каждый индекс массива добавляется в стек один раз, а значит, удалить из стека рекордов мы можем не более $n$ элементов за все время.

Вы можете обратить внимание, что в коде сверху мы используем std::vector вместо стека. Это связано с тем, что в более продвинутых контекстах нам может хотеться использовать не только последний элемент этого стека, но и иметь доступ к другим элементам. В зависимости от ситуации, как мы уже обсуждали, бывает полезно добавить изначально в стек элемент $-1$, и тогда проверка !stack_of_min.empty() превращается в stack_of_min.back() != -1. В частности, это может помочь избежать разбора случаев в задаче поиска суммы минимумов по всем подотрезкам массива, которую мы, собственно, уже почти решили. Мы научились искать для каждого элемента массива ближайший меньшей слева (это вершина стека перед добавлением индекса $i$ в конец). Теперь, чтобы найти ближайший справа меньший или равный, нужно запустить аналогичный цикл, но по убыванию индексов (положив изначально $n$ в стек), и удалять элементы, пока они строго больше $a_i$ (легко запомнить, что мы удаляем элементы, пока они не удовлетворяют необходимому нам условию: если нам нужен ближайший меньший, мы удаляем, пока он не меньше, если нам нужен ближайший меньший или равный, мы удаляем, пока он больше, если нам нужен ближайшие больший, мы удаляем, пока он не больше, если нам нужен ближайший больший или равный, мы удаляем, пока он меньше). Когда мы нашли те самые нужные индексы слева и справа, ответ на задачу выражается через них простой формулой, как мы уже говорили ранее. С полным решением задачи можно ознакомиться по ссылке.

Замечание:
Если мы кроме прохода со стеком минимумов еще сохраним для каждого индекса $i$ значение $pr_i$ (индекс ближайшего слева меньшего элемента), то чтобы позже восстановить стек минимумов индекса $i$, нужно рассмотреть индексы $i$, $pr_i$, $pr_{pr_i}$, $pr_{pr_{pr_i}}$, $ldots$. Тем самым, в случае, когда нам нужно иметь доступ к стеку минимумов в онлайне, мы можем воспользоваться идеей из персистентного Convex Hull Trick. Но в реальности в подавляющем числе случаев оффлайн прохода со стеком достаточно.

Стек минимумов без стека минимумов

Если нам нужно просто найти ближайший слева меньший элемент, то на самом деле явным образом хранить стек минимумов необязательно, ведь, как мы знаем, он как раз таки состоит из элементов $i$, $pr_i$, $pr_{pr_i}$, $pr_{pr_{pr_i}}$ и так далее. В таком случае удаление элемента с вершины стека равноценно переходу по ссылке $pr$ к следующему элементу. Такое решение выражается в следующий код:

// a -- array of length n
for (int i = 0; i < n; i++) {
    pr[i] = i - 1;
    while (pr[i] >= 0 && a[pr[i]] >= a[i]) {
        pr[i] = pr[pr[i]];
    }
}

Этот код на самом деле делает все те же самые операции, что и решение со стеком, поэтому тоже работает за линейное время. Однако нам не надо поддерживать стек, и код стал чище. По духу это решение похоже на алгоритм поиска префикс-функции.

Один проход со стеком вместо двух

Для простоты предположим, что все элементы массива различны. Тогда пока мы ищем ближайший слева элемент, меньший текущего, и удаляем элементы со стека, мы можем одновременно искать ближайший больший удаляемых элементов. Им будет наш текущий элемент, ведь мы как раз удаляем элемент с вершины стека ровно в тот момент, когда мы нашли меньший элемент.

// a -- array of length n
vector<int> stack_of_min;
vector<int> pr_l(n, -1); // closest smaller to the left
vector<int> pr_r(n, n); // closest smaller or equal to the right
for (int i = 0; i < n; i++) {
    while (!stack_of_min.empty() && a[stack_of_min.back()] >= a[i]) {
        pr_r[stack_of_min.back()] = i;
        stack_of_min.pop_back();
    }
    if (!stack_of_min.empty()) {
        pr_l[i] = stack_of_min.back();
    }
    stack_of_min.push_back(i);
}

В случае, если элементы в массиве могут совпадать, данный код найдет ближайший слева меньший и ближайший справа меньший или равный элементы массива. Для того чтобы и справа найти ближайший меньший, придется хранить дополнительную информацию в стеке. Но на самом деле, как и в примере с поиском суммы минимумов по всем подотрезкам, в большинстве задач мы как раз таки и хотим найти ближайший слева меньший, а справа меньший или равный.

Описанные выше два подхода можно объединить в один код, который без стека находит ближайший слева меньший и ближайший справа меньший или равный:

// a -- array of length n
vector<int> pr_l(n, -1); // closest smaller to the left
vector<int> pr_r(n, n); // closest smaller or equal to the right
for (int i = 0; i < n; i++) {
    pr_l[i] = i - 1;
    while (pr_l[i] >= 0 && a[pr_l[i]] >= a[i]) {
        pr_r[pr_l[i]] = i;
        pr_l[i] = pr_l[pr_l[i]];
    }
}

Применения

Мы уже обсудили, как стек минимумов может помочь нам решить задачу поиска суммы минимумов по всем подотрезкам массива за линейное время. Кроме того, его можно применить для ответа на запросы поиска минимума на отрезке в оффлайн. Далее мы приведем еще несколько примеров задач, в которых стек рекордов может заменить более сложные структуры данных.

Задача:
Дан массив $a_0$, $a_1$, $ldots$, $a_{n — 1}$. Необходимо для каждого индекса $i$ найти количество индексов $j$, таких что $a_j$ не меньше всех элементов, расположенных не дальше от $a_i$ чем $a_j$. Иными словами, количество индексов $j$, таких что не существует индекса $k$, такого что $|k — i| le |j — i|$ и $a_k > a_j$.

Заметим, что для индекса $i=n-1$ такие индексы $j$ — это в точности элементы стека нестрогих минимумов, потому что для индекса $n-1$ все другие индексы находятся слева, а условие на то, что все ближайшие элементы не меньше данного, как раз эквивалентно условию нахождения в стеке минимумов. Однако для других индексов все не так просто, потому что нас волнуют не только индексы слева, но и справа. Давайте снова перевернем задачу. Вместо того чтобы для индекса $i$ смотреть на все подходящие индексы $j$, мы для каждого индекса $j$ найдем, для каких индексов $i$ он подходит. Во-первых, на отрезке между $i$ и $j$ не должно быть никаких элементов, бóльших $a_j$. То есть, к примеру, $i$ должен быть не левее ближайшего слева числа, большего $a_j$, а также не правее ближайшего справа числа, большего $a_j$. Обозначим таким позиции за $l_0$ и $r_0$, как и раньше. Тогда на самом деле, если есть такой индекс $k$, который ломает желаемые нам условия, то он должен быть равен либо $l_0$, либо $r_0$, потому что все остальные индексы только дальше.
Тогда нам надо найти такие индексы $i$, чтобы для них $j$ был ближайшим индексом среди $j$, $l_0$ и $r_0$. Легко понять, что $i$ подходит тогда и только тогда, когда он лежит на отрезке $left[leftlceil frac{l_0 + j + 1}{2} rightrceil, leftlfloor frac{j + r_0 — 1}{2} rightrfloorright]$. То есть каждый индекс $j$ добавляет $1$ к ответу для какого-то отрезка индексов $i$, который мы можем легко посчитать, если посчитаем $l_0$ и $r_0$ для всех индексов, используя проходы со стеком максимумов слева направо и справа налево. Теперь нам лишь остается для каждого индекса $i$ понять, сколько отрезков через него проходят. Это можно без труда сделать, используя разностный массив и префиксные суммы. Таким образом, мы решили эту задачу за линейное время, избавившись от использования деревьев отрезков дважды: благодаря стеку максимумов и благодаря разностному массиву.

Задача:
Дано число $L$ и два массива положительных целых чисел $h_i$ и $w_i$. Необходимо посчитать значения динамики

$$dp_i = min_{j < i, w_{j + 1} + w_{j + 2} + ldots + w_i le L} dp_j + max(h_{j + 1}, h_{j + 2}, ldots, h_i)$$

Это задача с USACO 2012 US Open, Gold Division. Там представлена мотивация для такой странной динамики через легенду про организацию книг в полки с ограничением на ширину, минимизируя суммарную высоту полок.

Как же решать такую задачу? Заметим, что если мы можем упаковать $n$ книг с суммарной высотой полок $H$, то с такой же высотой мы можем упаковать и меньшее количество книг, поэтому $dp$ не убывает. Тогда если для двух индексов $j$ и $k$ значения максимума на отрезках $(j, i]$ и $(k, i]$ равны, то оптимально будет пересчитать динамику через меньший из этих двух индексов. Но максимум на отрезке $(j, i]$ — это как раз таки ближайший справа от $j$ элемент стека максимумов для индекса $i$. И если $j_1$ и $j_2$ — два последовательных индекса в стеке максимумов, таких что через них можно пересчитывать динамику, то среди всех индексов на $[j_1, j_2 — 1]$ оптимально пересчитывать динамику именно через $j_1$, потому что для них всех минимум на отрезке до $i$ равен $h_{j_2}$. Таким образом, практически всегда выгодно пересчитывать динамику через индексы из стека максимумов кроме одной ситуации: если через очередной элемент стека максимумов уже нельзя пересчитать динамику, потому что сумма ширин книг больше $L$. В таком случае мы можем использовать метод двух указателей, чтобы всегда поддерживать самый первый индекс $k$, через который мы все еще можем пересчитываться (используя массив префиксных сумм $w$ или поддерживая сумму ширин на отрезке $(k, i]$). Тогда мы будем поддерживать стек максимумов не как стек, а как дек, удаляя элементы из его начала, если через них уже нельзя пересчитываться, а также рядом со стеком максимумов хранить std::set значений $dp_{j} + h_{j’}$ для каждого элемента стека максимумов $j$, где $j’$ — следующий элемент стека максимумов.
Такие значения легко пересчитывать при удалении и добавлении элементов в стек. Тогда для того, чтобы посчитать значение $dp_i$, достаточно взять минимум из минимума значений сета и пересчета через индекс $k$, который мы поддерживаем двумя указателями. Итоговая асимптотика — $O(n log n)$.

И на самом деле, подобные динамики встречаются во многих других задачах. Ниже даны только несколько примеров задач, которые решаются абсолютно таким же методом. И в целом, стек рекордов — это очень простая и одновременно с этим очень мощная структура данных, которая может сэкономить вам написание дерева отрезков и лишний логарифм в асимптотике во многих задачах.

Источники

  • Статья о том, что можно использовать стек минимумов без стека минимумов.
  • Пост про параллельный подсчет ближайшего меньшего слева и справа.

Задачи для практики

  • Задача на поиск суммы минимумов по всем подотрезкам массива.
  • Задача с USACO 2012 US Open, Gold Division, разобранная выше.
  • Задача (спойлеры к петрозаводским сборам), аналогичная последней разобранной задаче выше.
  • Еще одна задача, аналогичная последней разобранной задаче выше.
  • Задача, в которой применяется идея, схожая с построением одновременно ближайших слева и справа меньших.
  • Коллекция задач на стек минимумов.

Понравилась статья? Поделить с друзьями:
  • Как сбербанк онлайн найти расчетный счет карты
  • Обед дома как составить меню
  • Как найти синус угла 390 градусов
  • Где на айфоне найти айфон как отключить
  • Как найти объем капель