I am just wondering, whether it's possible to implement a structure (list/deque) which would have O(1)
complexity (const/amortized doesn't matter) for get_min(), push_back(), push_front(), pop_back(), pop_front()
operations?
It's definitely possible to implement stack or even queue (for them push()
and pop()
are available only for one end) which will satisfy these conditions. But I wasn't able to reuse this logic to a dequeue case.
It's also possible to create structure which will have O(logn)
complexity for pushes/pops and O(1)
for get_min
(just using simple list and min_heap
in which deletion of arbitrary element for O(logn)
is available).
But still amortized O(1)
for all operations seems impossible for me. If that is the case, can the information about exact number of operations (or max possible number of elements in list) n
helps (simpler case)? Can we somehow use O(n)
(or even more) additional memory or smth like this?
It is indeed possible to build a min-deque where the push-front, push-back, and find-min operations work in worst-case time O(1) and the pop-front and pop-back operations take amortized time O(1). The easiest route that I know goes like this:
In case you haven’t seen how to do step (2), the idea is a generalization of how to make a queue from two stacks. You maintain two stacks representing the elements of the deque such that one stack represents the front of the deque in reverse order (stack top is the front of the deque, and deeper elements are the next elements of the deque) and the elements of the other stack represent the back of the deque (top element is the last element of the deque, deeper elements go backwards through the deque). For example, here’s one way to encode the deque 1, 2, 3, ..., 10:
front: | 7 6 5 4 3 2 1
back: | 8 9 10
To push onto the front or back of the deque, just do a push onto the appropriate deque. To pop the front or back of the deque, if the appropriate stack is nonempty, just pop from it. Otherwise, if the stack is empty, you need to move some number of elements from the other stack over. Specifically, to keep things balanced between the two stacks, you do a series of pushes and pops to put half the elements into each of the two stacks. That looks like this:
You can show using a potential argument (see Φ to be the absolute value of the difference in heights of the two stacks) that each operation takes amortized time O(1).
It may be possible to somehow speed this up to worst-case O(1) time per operation using some sort of scheduling scheme, but I’m not sure how to do this. I know there’s a way to implement a queue with four stacks with worst-case O(1) for each operation, so perhaps this idea could be generalized to work here?