包含min函数的栈 & 滑动窗口最大值

1.包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

 解析:

对于普通栈的push()和pop()函数的复杂度为O(1);而获取最小值的min() 函数需要遍历整个栈,复杂度为 O(N)。

本题的难点在于如何保证min()函数复杂度降维O(1)

可借助辅助栈实现:

数据栈 A : 栈 A 用于存储所有元素,保证入栈 push() 函数、出栈 pop() 函数、获取栈顶 top() 函数的正常逻辑。

辅助栈 B : 栈 B 中存储栈 A 中所有 非严格降序 元素的子序列,则栈 A 中的最小元素始终对应栈 B 的栈顶元素。此时,min() 函数只需返回栈 B 的栈顶元素

因此,只需设法维护好 栈 B 的元素,使其保持是栈 A 的非严格降序元素的子序列,即可实现 min() 函数的 O(1) 复杂度。

 函数设计:

  1.push(x)函数:对A push操作的同时也要对B push操作,重点保证栈B的元素非是非严格降序的

    • 执行元素x压入栈A
    • 栈B为空或x<=栈B的栈顶元素,则执行元素x压入栈B

  2.pop()函数:重点为保持栈A,栈B的元素一致性,始终让栈B中栈顶的元素是当前栈A中元素的最小值

    •  执行栈A元素出栈,将出栈元素记为y
    •  若y等于栈B的栈顶元素,则执行栈B元素出栈

  3.top() 函数: 直接返回栈 A 的栈顶元素,即返回 A.peek() ;

  4.min() 函数: 直接返回栈 B 的栈顶元素,即返回 B.peek() ;

python

class MinStack:
    def __init__(self):
        self.A, self.B = [], []

    def push(self, x: int) -> None:
        self.A.append(x)
        if not self.B or self.B[-1] >= x:
            self.B.append(x)

    def pop(self) -> None:
        if self.A.pop() == self.B[-1]:
            self.B.pop()

    def top(self) -> int:
        return self.A[-1]

    def min(self) -> int:
        return self.B[-1]

  Java

class MinStack {
    Stack<Integer> A, B;
    public MinStack() {
        A = new Stack<>();
        B = new Stack<>();
    }
    public void push(int x) {
        A.add(x);
        if(B.empty() || B.peek() >= x)
            B.add(x);
    }
    public void pop() {
        if(A.pop().equals(B.peek()))
            B.pop();
    }
    public int top() {
        return A.peek();
    }
    public int min() {
        return B.peek();
    }
}

2.滑动窗口最大值

 解析:

本题与包含min函数的栈这个题目的区别在于“出栈操作”删除的“列表尾部元素”“滑动窗口”删除的是“列表首部元素”

 

 

本题使用 单调队列 即可解决以上问题。遍历数组时,每轮保证单调队列 deque :

deque 内 仅包含窗口内的元素⇒ 每轮窗口滑动移除了元素 nums[i - 1],需将 deque内的对应元素一起删除。
deque 内的元素 非严格递减 ⇒ 每轮窗口滑动添加了元素 nums[j + 1],需将 deque内所有 < nums[j + 1] 的元素删除。

 

 python

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        if not nums or k == 0:
            return []
        
        deque = collections.deque()
        # 未形成窗口前
        for i in range(k):
            while deque and deque[-1] < nums[i]:  # 保证队首是最大的
                deque.pop()
            deque.append(nums[i])

        res = [deque[0]]

        # 形成窗口后
        for i in range(k, len(nums)):
            if deque[0] == nums[i-k]:  # 将队首等于已删除的元素去掉
                deque.popleft()
            while deque and deque[-1] < nums[i]:
                deque.pop()
            deque.append(nums[i])
            res.append(deque[0])
        return res

  Java

// 单调队列的实现
class MonotonicQueue {
    LinkedList<Integer> q = new LinkedList<>();
    public void push(int n) {
        // 将比n小的元素全部删除,保持队列的头是最大的
        while (!q.isEmpty() && q.getLast() < n) {
            q.pollLast();
        }
        // 将n加入尾部
        q.addLast(n);
    }

    // 前面的push方法已经保证了队列的头是最大的,因此获取最大值,只需获取队列的头部就行了
    public int max() {
        return q.getFirst();
    }

    // 删除队列头的元素,这里的删除,是遇到新加的数字与队列头的数字相同的情况
    public void pop(int n) {
        if (n == q.getFirst()) {
            q.pollFirst();
        }
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MonotonicQueue window = new MonotonicQueue();
        // 集合
        List<Integer> res = new ArrayList<>();

        for (int i=0; i < nums.length; i++) {
            if (i < k-1) {
                // 先填满窗口的前k-1
                window.push(nums[i]);
            } else {
                // 窗口向前滑动,加入新数字
                window.push(nums[i]);
                // 获取窗口中的最大值
                res.add(window.max());
                // 弹出旧的数字(这个旧数字如果与新的数字相同)
                window.pop(nums[i-k+1]);
            }
        }

        // 返回值要求的是返回数组int[]
        int[] arr = new int[res.size()];
        for(int i=0; i < res.size(); i++) {
            arr[i] = res.get(i);
        }

        return arr;

    }
}

  参考:

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58o46i/

 

posted @ 2021-03-03 10:24  GumpYan  阅读(92)  评论(0编辑  收藏  举报