【数据结构-栈】【差分思想-延迟更新优化】力扣1381. 设计一个支持增量操作的栈

请你设计一个支持对其元素进行增量操作的栈。

实现自定义栈类 CustomStack :

CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量。

void push(int x):如果栈还未增长到 maxSize ,就将 x 添加到栈顶。

int pop():弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1 。

void inc(int k, int val):栈底的 k 个元素的值都增加 val 。如果栈中元素总数小于 k ,则栈中的所有元素都增加 val 。

示例:

输入:

"CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"

\[3\],\[1\],\[2\],\[\],\[2\],\[3\],\[4\],\[5,100\],\[2,100\],\[\],\[\],\[\],\[\]

输出:

null,null,null,2,null,null,null,null,null,103,202,201,-1

解释:

CustomStack stk = new CustomStack(3); // 栈是空的 []

stk.push(1); // 栈变为 [1]

stk.push(2); // 栈变为 [1, 2]

stk.pop(); // 返回 2 --> 返回栈顶值 2,栈变为 [1]

stk.push(2); // 栈变为 [1, 2]

stk.push(3); // 栈变为 [1, 2, 3]

stk.push(4); // 栈仍然是 [1, 2, 3],不能添加其他元素使栈大小变为 4

stk.increment(5, 100); // 栈变为 [101, 102, 103]

stk.increment(2, 100); // 栈变为 [201, 202, 103]

stk.pop(); // 返回 103 --> 返回栈顶值 103,栈变为 [201, 202]

stk.pop(); // 返回 202 --> 返回栈顶值 202,栈变为 [201]

stk.pop(); // 返回 201 --> 返回栈顶值 201,栈变为 []

stk.pop(); // 返回 -1 --> 栈为空,返回 -1

提示:

1 <= maxSize, x, k <= 1000

0 <= val <= 100

每种方法 increment,push 以及 pop 分别最多调用 1000 次

模拟栈

cpp 复制代码
class CustomStack {
public:
    vector<int> stk;
    int top;
    CustomStack(int maxSize) {
        stk.resize(maxSize);
        top = -1;
    }
    
    void push(int x) {
        if(top != stk.size()-1){
            top++;
            stk[top] = x;
        }
    }
    
    int pop() {
        if(top == -1){
            return -1;
        }
        top--;
        return stk[top+1];
    }
    
    void increment(int k, int val) {
        int lim = min(k, top+1);
        for(int i = 0; i < lim; i++){
            stk[i] += val;
        }
    }
};

时间复杂度:初始化(构造函数)、push 操作和 pop 操作的渐进时间复杂度为 O(1),inc 操作的渐进时间复杂度为 O(k)。

空间复杂度:这里用到了一个长度为 maxSize 的数组作为辅助空间,渐进空间复杂度为 O(maxSize)。

使用vector来模拟栈的过程,区别在于我们不用真正的推出元素,只需将top--,总体过程相对简单。

使用差分优化(延迟更新)

cpp 复制代码
class CustomStack {
public:
    vector<int> stk, add;
    int top;
    CustomStack(int maxSize) {
        stk.resize(maxSize);
        add.resize(maxSize);
        top = -1;
    }
    
    void push(int x) {
        if(top != stk.size()-1){
            top++;
            stk[top] = x;
        }
    }
    
    int pop() {
        if(top == -1){
            return -1;
        }
        
        int ans = stk[top] + add[top];
        if(top > 0){
            add[top-1] += add[top];
        }
        add[top] = 0;
        top--;
        return ans;
    }
    
    void increment(int k, int val) {
        int lim = min(k-1, top);
        if(lim >= 0){
            add[lim] += val;
        }
    }
};

时间复杂度:所有操作的渐进时间复杂度均为 O(1)。

空间复杂度:这里用到了两个长度为 maxSize 的数组作为辅助空间,渐进空间复杂度为 O(maxSize)。

我们可以发现在increment函数中,我们第一种方法采用的是选择每次调用这个函数,就将前k个值进行增量。那既然都要增量,我们可不可以先统计完增量,然后再需要pop栈顶值的时候,再把增量加给需要pop的元素呢。比如说我们要将前k个数增加100次val,那么就要循环100k次,那么我们可以统计一共前k个数要增加100,那么最后将前k个数都添加100,或许只需要循环k次,甚至更少呢?

所以我们采用延迟更新的思想,将增量储存在一个动态数组add中,在每次计算增量的时候,我们只需要计算add[k-1]的增量(如果k-1小于top的话)。那么我们如何运用add呢,我们会发现我们只有在pop的时候,我们弹出的栈顶元素值时,需要增量和原来的栈顶元素相加。也就是int ans = stk[top] + add[top];。这时候add[top]的增量被使用了,我们就将他传递给add[top-1],对应代码add[top-1] += add[top];

举个例子,比如说我们进行了两次increment操作,第一次将前k+1个元素增加val1,也就是add[k]=val1。第二次将前k+3个元素增加了val2,也就是add[k+2]=val2,那么他们共同覆盖的范围是不是前k+1个元素,那么也就是说,前k+1个元素增量是val1+val2,在第k+2到k+3个元素,增量是val2。这时候假设我们进行三次pop()操作,那么stk[k+2] += add[k+2],然后add[k+2]将增量传给add[k+1](使用+=),这时候stk[k+1] += add[k+1],依旧是加上val2。

这时候我们再将add[k+1]传给add[k],这时候add[k]就是val1+val2,也就是两次increment操作共同覆盖的区域,正好符合我们上面讲到的前k+1个元素的增量是val1+val2。

使用完增量后,记得将add[top]设为0,然后让top--。

相关推荐
子春一6 分钟前
Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
算法·flutter·游戏
草履虫建模6 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq8 小时前
分布式系统安全通信
开发语言·c++·算法
Jasmine_llq9 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
爱吃rabbit的mq9 小时前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习
(❁´◡`❁)Jimmy(❁´◡`❁)10 小时前
Exgcd 学习笔记
笔记·学习·算法
YYuCChi10 小时前
代码随想录算法训练营第三十七天 | 52.携带研究材料(卡码网)、518.零钱兑换||、377.组合总和IV、57.爬楼梯(卡码网)
算法·动态规划
不能隔夜的咖喱11 小时前
牛客网刷题(2)
java·开发语言·算法
VT.馒头11 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
进击的小头11 小时前
实战案例:51单片机低功耗场景下的简易滤波实现
c语言·单片机·算法·51单片机