LeetCode 716. 最大栈

716. 最大栈

设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。

实现 MaxStack 类:

  • MaxStack() 初始化栈对象
  • void push(int x) 将元素 x 压入栈中。
  • int pop() 移除栈顶元素并返回这个元素。
  • int top() 返回栈顶元素,无需移除。
  • int peekMax() 检索并返回栈中最大元素,无需移除。
  • int popMax() 检索并返回栈中最大元素,并将其移除。如果有多个最大元素,只要移除 最靠近栈顶 的那个。

示例:

输入
["MaxStack", "push", "push", "push", "top", "popMax", "top", "peekMax", "pop", "top"]
[[], [5], [1], [5], [], [], [], [], [], []]
输出
[null, null, null, null, 5, 5, 1, 5, 1, 5]

解释
MaxStack stk = new MaxStack();
stk.push(5);   // [5] - 5 既是栈顶元素,也是最大元素
stk.push(1);   // [5, 1] - 栈顶元素是 1,最大元素是 5
stk.push(5);   // [5, 1, 5] - 5 既是栈顶元素,也是最大元素
stk.top();     // 返回 5,[5, 1, 5] - 栈没有改变
stk.popMax();  // 返回 5,[5, 1] - 栈发生改变,栈顶元素不再是最大元素
stk.top();     // 返回 1,[5, 1] - 栈没有改变
stk.peekMax(); // 返回 5,[5, 1] - 栈没有改变
stk.pop();     // 返回 1,[5] - 此操作后,5 既是栈顶元素,也是最大元素
stk.top();     // 返回 5,[5] - 栈没有改变

提示:

  • -10^7 <= x <= 10^7
  • 最多调用 10^4 次 pushpoptoppeekMax 和 popMax
  • 调用 poptoppeekMax 或 popMax 时,栈中 至少存在一个元素

进阶: 

  • 试着设计解决方案:调用 top 方法的时间复杂度为 O(1) ,调用其他方法的时间复杂度为 O(logn) 。 

解法1:堆与延迟更新
 

要快速查看或弹出最大元素,我们可能会想到一个堆(或优先队列)。同时,一个经典的堆栈足以快速查看或弹出最后添加的元素。如果我们同时保持两种数据结构呢?

是的,我们可以快速弹出最大或最后一个元素。然而,当我们弹出堆或栈的顶部元素时,我们不知道如何找到在其他元素中删除的元素,除非我们从顶部到底部列举出所有项。

因此,我们不急于删除弹出的元素。相反,我们只记下这个元素的 ID。下次,当我们准备查看或弹出堆或栈的顶部时,我们首先检查它的 ID 是否已经从其他数据结构中删除。


为了记住所有被删除元素的 ID,我们使用一个哈希集 removed 来存储它们。除了我们提到的堆栈(stack)和最大堆(heap),我们还需要一个像方法一一样的计数器 idx 来给每个元素打一个独特的 ID。

每当 push(x) 被调用时,我们将它连同当前的计数器值一起添加到 heap 和 stack 中,然后将我们的 idx 加 1。

每当我们被请求操作 stack 或 heap(即,top,pop,peekMax 和 popMax),我们首先检查其顶部元素的 ID,如果它恰好是 removed 中的一个 ID,即它已经被删除,我们需要 移除当前的顶部元素,直到其 ID 不在 removed 中,以确保顶部仍然存在。之后,

对于 top,返回 stack 顶部元素的值。
对于 peekMax,返回 heap 顶部元素的值。
对于 pop,移除 stack 的顶部元素,将其 ID 加入 removed,并返回其值。
对于 popMax,移除 heap 的顶部元素,将其 ID 加入 removed,并返回其值。
我们可以观察到,我们只检查顶部元素的存在,并且只在顶部元素是顶部时移除元素,因为对堆栈或堆的顶部元素的删除操作要快得多。

Java版:

class MaxStack {
    private Stack<int[]> stack;
    private Queue<int[]> heap;
    private Set<Integer> removed;
    private int idx;

    public MaxStack() {
        stack = new Stack<>();
        // 值相同时,下标大的排前边;否则值大的排前边
        heap = new PriorityQueue<>((a, b) -> a[0] == b[0] ? b[1] - a[1] : b[0] - a[0]);
        removed = new HashSet<>();
    }
    
    public void push(int x) {
        stack.add(new int[]{x, idx});
        heap.add(new int[]{x, idx});
        idx++;
    }
    
    public int pop() {
        while (removed.contains(stack.peek()[1])) {
            stack.pop();
        }
        int[] top = stack.pop();
        removed.add(top[1]);
        return top[0];
    }
    
    public int top() {
        while (removed.contains(stack.peek()[1])) {
            stack.pop();
        }
        return stack.peek()[0];
    }
    
    public int peekMax() {
        while (removed.contains(heap.peek()[1])) {
            heap.poll();
        }
        return heap.peek()[0];
    }
    
    public int popMax() {
        while (removed.contains(heap.peek()[1])) {
            heap.poll();
        }
        int[] top = heap.poll();
        removed.add(top[1]);
        return top[0];
    }
}

/**
 * Your MaxStack object will be instantiated and called as such:
 * MaxStack obj = new MaxStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.peekMax();
 * int param_5 = obj.popMax();
 */

或:

class MaxStack {
    private Deque<int[]> stack;
    private Queue<int[]> heap;
    private Set<Integer> removed;
    private int idx;

    public MaxStack() {
        stack = new LinkedList<>();
        // 值相同时,下标大的排前边;否则值大的排前边
        heap = new PriorityQueue<>((a, b) -> a[0] == b[0] ? b[1] - a[1] : b[0] - a[0]);
        removed = new HashSet<>();
    }
    
    public void push(int x) {
        stack.push(new int[]{x, idx});
        heap.add(new int[]{x, idx});
        idx++;
    }
    
    public int pop() {
        while (removed.contains(stack.peek()[1])) {
            stack.pop();
        }
        int[] top = stack.pop();
        removed.add(top[1]);
        return top[0];
    }
    
    public int top() {
        while (removed.contains(stack.peek()[1])) {
            stack.pop();
        }
        return stack.peek()[0];
    }
    
    public int peekMax() {
        while (removed.contains(heap.peek()[1])) {
            heap.poll();
        }
        return heap.peek()[0];
    }
    
    public int popMax() {
        while (removed.contains(heap.peek()[1])) {
            heap.poll();
        }
        int[] top = heap.poll();
        removed.add(top[1]);
        return top[0];
    }
}

/**
 * Your MaxStack object will be instantiated and called as such:
 * MaxStack obj = new MaxStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.peekMax();
 * int param_5 = obj.popMax();
 */

Python3版:

class MaxStack:

    def __init__(self):
        self.stack = []
        self.heap = []
        self.removed = set()
        self.idx = 0


    def push(self, x: int) -> None:
        self.stack.append([x, self.idx])
        heapq.heappush(self.heap, [-x, -self.idx])
        self.idx += 1


    def pop(self) -> int:
        while self.stack and self.stack[-1][1] in self.removed:
            self.stack.pop()
        num, i = self.stack.pop()
        self.removed.add(i)
        return num


    def top(self) -> int:
        while self.stack and self.stack[-1][1] in self.removed:
            self.stack.pop()
        return self.stack[-1][0]


    def peekMax(self) -> int:
        while self.heap and -self.heap[0][1] in self.removed:
            heapq.heappop(self.heap)
        return -self.heap[0][0]


    def popMax(self) -> int:
        while self.heap and -self.heap[0][1] in self.removed:
            heapq.heappop(self.heap)
        num, i = heapq.heappop(self.heap)
        self.removed.add(-i)
        return -num


# Your MaxStack object will be instantiated and called as such:
# obj = MaxStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.peekMax()
# param_5 = obj.popMax()

复杂度分析

设 N 为要添加到堆栈的元素数量。

  • 时间复杂度:
  • push: O(logN),将元素添加到 heap 需要 O(logN),将元素添加到 stack 需要 O(1)。
  • 由于单个 pop/popMax 调用导致的操作的摊销时间复杂度为 O(logN)。对于一个 pop 调用,我们首先在 stack 中移除最后一个元素并将其 ID 加入 removed,可以在 O(1) 内完成,并且在未来(当 peekMax 或 popMax 被调用时)会在 heap 中删除顶部元素,这需要 logN 的时间复杂度。类似地,popMax 立即需要 O(logN),以及在后续操作中需要 O(1)。请注意,因为我们对两个数据结构进行了懒惰更新,未来的操作在某些情况下可能永远不会发生。但即使在最坏的情况下,摊销时间复杂度的上限仍然只有 O(logN)。
  • top:O(1),不包括我们上面讨论的与 popMax 调用相关的时间成本。
  • peekMax:O(logN),不包括我们上面讨论的与 pop 调用相关的时间成本。
  • 空间复杂度:O(N),heap、stack 和 removed 的最大大小。
  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值