Skip to content

题目链接 · 灵神原题解(署名来源)

方法一:额外保存前缀最小值

引入

给你一个数组 nums,如何计算每个前缀的最小值?

定义 preMin[i] 表示 nums[0]nums[i] 的最小值。

这可以从左到右计算:

  • preMin[0]=nums[0]
  • preMin[1]=min(nums[0],nums[1])
  • preMin[2]=min(nums[0],nums[1],nums[2])=min(preMin[1],nums[2])
  • preMin[3]=min(nums[0],nums[1],nums[2],nums[3])=min(preMin[2],nums[3])
  • ……

一般地,我们有

preMin[i]=min(preMin[i1],nums[i])

回到本题

nums 视作栈,本题相当于在 nums 的末尾动态地添加/删除元素

  • 栈中除了保存添加的元素,还保存前缀最小值。(栈中保存的是 pair)
  • 添加元素:设当前栈的大小是 n。添加元素 val 后,额外维护 preMin[n]=min(preMin[n1],val),其中 preMin[n1] 是添加 val 之前,栈顶保存的前缀最小值。
  • 删除元素:弹出栈顶即可。

细节

一开始栈为空(n=0),添加 val 时,我们没有对应的 preMin[n1]。需要特判栈为空的情况吗?

不需要。初始化的时候,在栈底加一个 哨兵,作为 preMin[1]

:题目保证 pop,top,getMin 都是在非空栈上操作的。

python
class MinStack:
    def __init__(self):
        # 这里的 0 写成任意数都可以,反正用不到
        self.st = [(0, inf)]  # 栈底哨兵

    def push(self, val: int) -> None:
        self.st.append((val, min(self.st[-1][1], val)))

    def pop(self) -> None:
        self.st.pop()

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

    def getMin(self) -> int:
        return self.st[-1][1]
cpp
// C++ 版待补充
cpp
class MinStack {
    stack<pair<int, int>> st;

public:
    MinStack() {
        // 添加栈底哨兵 INT_MAX
        // 这里的 0 写成任意数都可以,反正用不到
        st.emplace(0, INT_MAX);
    }

    void push(int val) {
        st.emplace(val, min(getMin(), val)); 
    }

    void pop() {
        st.pop();
    }

    int top() {
        return st.top().first;
    }

    int getMin() {
        return st.top().second;
    }
};

复杂度分析

  • 时间复杂度:所有操作均为 O(1):严格地说,push 是均摊 O(1)
  • 空间复杂度:O(q)。其中 qpush 调用的次数。最坏情况下,只有 push 操作,需要 O(q) 的空间保存元素。

方法二:保存差值

进阶问题:如果不允许额外保存前缀最小值,栈中只能保存整数(不能保存数对),怎么做?

如果栈中保存的是 val 与前缀最小值的差值,那么只要我们能实时维护前缀最小值,就能通过差值还原 val

例如依次插入 val=5,6,8,1,2,计算过程如下表。

如何阅读下表

  1. 表格中的差值等于插入的 val 减去插入之前的最小值。
  2. push(val) 从上到下阅读,toppop 从下到上阅读。
push(val)最小值差值toppop
$ $$ $$ $$ $
55最小值最小值增加
651最小值+差值最小值不变
853最小值+差值最小值不变
114最小值最小值增加 4
211最小值+差值最小值不变

一般地:

  • 初始化前缀最小值 mn=
  • push(val):先把 (valmn) 入栈,再更新 mnmin(mn,val)
  • top:返回 mn+max(,0)。如果栈顶大于 0,说明 valmn 多一个栈顶的值;否则 val 就是 mn
  • pop:把 mn 减少 min(,0)。如果栈顶小于 0,这会把 mn 增大;否则 mn 不变。
  • getMin:返回 mn 即可。

代码实现时,为避免溢出,需要用 64 位整数。

:题目保证 pop,top,getMin 都是在非空栈上操作的。

python
class MinStack:
    def __init__(self):
        self.st = []
        self.mn = inf

    def push(self, val: int) -> None:
        # 栈中保存 val - 之前的最小值
        self.st.append(val - self.mn)
        self.mn = min(self.mn, val)

    def pop(self) -> None:
        # 如果栈顶是负数,增大 mn,否则不变
        self.mn -= min(self.st.pop(), 0)

    def top(self) -> int:
        # 如果栈顶是正数,说明实际的 val 比 mn 大,否则 val 等于 mn
        return self.mn + max(self.st[-1], 0)

    def getMin(self) -> int:
        return self.mn
cpp
// C++ 版待补充
cpp
class MinStack {
    stack<long long> st;
    long long mn = LLONG_MAX / 2; // 避免 val - mn 溢出

public:
    void push(int val) {
        // 栈中保存 val - 之前的最小值
        st.push(val - mn);
        mn = min(mn, 1LL * val);
    }

    void pop() {
        // 如果栈顶是负数,增大 mn,否则不变
        mn -= min(st.top(), 0LL);
        st.pop();
    }

    int top() {
        // 如果栈顶是正数,说明实际的 val 比 mn 大,否则 val 等于 mn
        return mn + max(st.top(), 0LL);
    }

    int getMin() {
        return mn;
    }
};

复杂度分析

  • 时间复杂度:所有操作均为 O(1):严格地说,push 是均摊 O(1)
  • 空间复杂度:O(q)。其中 qpush 调用的次数。最坏情况下,只有 push 操作,需要 O(q) 的空间保存元素。

变形题

  1. 改成队列(queue),getMin 返回队列中的最小元素。
  2. 改成双端队列(deque),getMin 返回双端队列中的最小元素。

解答:[Tutorial] Minimum Deque

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

本文整理自灵茶山艾府(endlesscheng)的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传