|
| 1 | +# 题目地址(1381. 设计一个支持增量操作的栈) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/design-a-stack-with-increment-operation/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +请你设计一个支持下述操作的栈。 |
| 9 | +
|
| 10 | +实现自定义栈类 CustomStack : |
| 11 | +
|
| 12 | +CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量,栈在增长到 maxSize 之后则不支持 push 操作。 |
| 13 | +void push(int x):如果栈还未增长到 maxSize ,就将 x 添加到栈顶。 |
| 14 | +int pop():弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1 。 |
| 15 | +void inc(int k, int val):栈底的 k 个元素的值都增加 val 。如果栈中元素总数小于 k ,则栈中的所有元素都增加 val 。 |
| 16 | +
|
| 17 | +
|
| 18 | +示例: |
| 19 | +
|
| 20 | +输入: |
| 21 | +["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"] |
| 22 | +[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]] |
| 23 | +输出: |
| 24 | +[null,null,null,2,null,null,null,null,null,103,202,201,-1] |
| 25 | +解释: |
| 26 | +CustomStack customStack = new CustomStack(3); // 栈是空的 [] |
| 27 | +customStack.push(1); // 栈变为 [1] |
| 28 | +customStack.push(2); // 栈变为 [1, 2] |
| 29 | +customStack.pop(); // 返回 2 --> 返回栈顶值 2,栈变为 [1] |
| 30 | +customStack.push(2); // 栈变为 [1, 2] |
| 31 | +customStack.push(3); // 栈变为 [1, 2, 3] |
| 32 | +customStack.push(4); // 栈仍然是 [1, 2, 3],不能添加其他元素使栈大小变为 4 |
| 33 | +customStack.increment(5, 100); // 栈变为 [101, 102, 103] |
| 34 | +customStack.increment(2, 100); // 栈变为 [201, 202, 103] |
| 35 | +customStack.pop(); // 返回 103 --> 返回栈顶值 103,栈变为 [201, 202] |
| 36 | +customStack.pop(); // 返回 202 --> 返回栈顶值 202,栈变为 [201] |
| 37 | +customStack.pop(); // 返回 201 --> 返回栈顶值 201,栈变为 [] |
| 38 | +customStack.pop(); // 返回 -1 --> 栈为空,返回 -1 |
| 39 | +
|
| 40 | +
|
| 41 | +提示: |
| 42 | +
|
| 43 | +1 <= maxSize <= 1000 |
| 44 | +1 <= x <= 1000 |
| 45 | +1 <= k <= 1000 |
| 46 | +0 <= val <= 100 |
| 47 | +每种方法 increment,push 以及 pop 分别最多调用 1000 次 |
| 48 | +``` |
| 49 | + |
| 50 | +## 前置知识 |
| 51 | + |
| 52 | +- 栈 |
| 53 | +- 前缀和 |
| 54 | + |
| 55 | +## increment 时间复杂度为 $O(k)$ 的方法 |
| 56 | + |
| 57 | +### 思路 |
| 58 | + |
| 59 | +首先我们来看一种非常符合直觉的方法,然而这种方法并不好,increment 操作需要的时间复杂度为 $O(k)$。 |
| 60 | + |
| 61 | +`push`和 `pop` 就是普通的栈操作。 唯一要注意的是边界条件,这个已经在题目中指明了,具体来说就是: |
| 62 | + |
| 63 | +- push 的时候要判断是否满了 |
| 64 | +- pop 的时候要判断是否空了 |
| 65 | + |
| 66 | +而做到上面两点,只需要一个 cnt 变量记录栈的当前长度,一个 size 变量记录最大容量,并在 pop 和 push 的时候更新 cnt 即可。 |
| 67 | + |
| 68 | +### 代码 |
| 69 | + |
| 70 | +```py |
| 71 | +class CustomStack: |
| 72 | + |
| 73 | + def __init__(self, size: int): |
| 74 | + self.st = [] |
| 75 | + self.cnt = 0 |
| 76 | + self.size = size |
| 77 | + |
| 78 | + def push(self, x: int) -> None: |
| 79 | + if self.cnt < self.size: |
| 80 | + self.st.append(x) |
| 81 | + self.cnt += 1 |
| 82 | + |
| 83 | + |
| 84 | + def pop(self) -> int: |
| 85 | + if self.cnt == 0: return -1 |
| 86 | + self.cnt -= 1 |
| 87 | + return self.st.pop() |
| 88 | + |
| 89 | + |
| 90 | + def increment(self, k: int, val: int) -> None: |
| 91 | + for i in range(0, min(self.cnt, k)): |
| 92 | + self.st[i] += val |
| 93 | + |
| 94 | +``` |
| 95 | + |
| 96 | +**复杂度分析** |
| 97 | + |
| 98 | +- 时间复杂度:push 和 pop 操作的时间复杂度为 $O(1)$(讲义有提到),而 increment 操作的时间复杂度为 $O(min(k, cnt))$ |
| 99 | +- 空间复杂度:$O(1)$ |
| 100 | + |
| 101 | +## 前缀和 |
| 102 | + |
| 103 | +前缀和在讲义里面提到过,大家也可是看下我的文章 [一次搞定前缀和](https://lucifer.ren/blog/2020/09/27/atMostK/) |
| 104 | + |
| 105 | +### 思路 |
| 106 | + |
| 107 | +和上面的思路类似,不过我们采用空间换时间的方式。采用一个额外的数组 incrementals 来记录每次 incremental 操作。 |
| 108 | + |
| 109 | +具体算法如下: |
| 110 | + |
| 111 | +- 初始化一个大小为 maxSize 的数组 incrementals, 并全部填充 0 |
| 112 | +- push 操作不变,和上面一样 |
| 113 | +- increment 的时候,我们将用到 incremental 信息。那么这个信息是什么,从哪来呢?我这里画了一个图 |
| 114 | + |
| 115 | + |
| 116 | + |
| 117 | +如图黄色部分是我们需要执行增加操作,我这里画了一个挡板分割,实际上这个挡板不存在。那么如何记录黄色部分的信息呢?我举个例子来说 |
| 118 | + |
| 119 | +比如: |
| 120 | + |
| 121 | +- 调用了 increment(3, 2),就把 increment[3] 增加 2。 |
| 122 | +- 继续调用 increment(2, 5),就把 increment[2] 增加 5。 |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +而当我们 pop 的时候: |
| 127 | + |
| 128 | +- 只需要将栈顶元素**加上 increment[cnt - 1]** 即可, 其中 cnt 为栈当前的大小。 |
| 129 | +- 另外,我们需要将 increment[cnt - 1] 更新到 increment[cnt - 2],并将 increment[cnt - 1] 重置为 0。 |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +### 代码 |
| 134 | + |
| 135 | +```py |
| 136 | +class CustomStack: |
| 137 | + |
| 138 | + def __init__(self, size: int): |
| 139 | + self.st = [] |
| 140 | + self.cnt = 0 |
| 141 | + self.size = size |
| 142 | + self.incrementals = [0] * size |
| 143 | + |
| 144 | + def push(self, x: int) -> None: |
| 145 | + if self.cnt < self.size: |
| 146 | + self.st.append(x) |
| 147 | + self.cnt += 1 |
| 148 | + |
| 149 | + |
| 150 | + def pop(self) -> int: |
| 151 | + if self.cnt == 0: return -1 |
| 152 | + if self.cnt >= 2: |
| 153 | + self.incrementals[self.cnt - 2] += self.incrementals[self.cnt - 1] |
| 154 | + ans = self.st.pop() + self.incrementals[self.cnt - 1] |
| 155 | + self.incrementals[self.cnt - 1] = 0 |
| 156 | + self.cnt -= 1 |
| 157 | + return ans |
| 158 | + |
| 159 | + |
| 160 | + def increment(self, k: int, val: int) -> None: |
| 161 | + if self.cnt: |
| 162 | + self.incrementals[min(self.cnt, k) - 1] += val |
| 163 | +``` |
| 164 | + |
| 165 | +**复杂度分析** |
| 166 | + |
| 167 | +- 时间复杂度:全部都是 $O(1)$ |
| 168 | +- 空间复杂度:我们维护了一个大小为 maxSize 的数组,因此平均到每次的空间复杂度为 $O(maxSize / N)$,其中 N 为操作数。 |
| 169 | + |
| 170 | +## 优化的前缀和 |
| 171 | + |
| 172 | +### 思路 |
| 173 | + |
| 174 | +上面的思路无论如何,我们都需要维护一个大小为 $O(maxSize)$ 的数组 incremental 。而由于栈只能在栈顶进行操作,因此这实际上可以稍微优化一点,即维护一个大小为当前栈长度的 incrementals,而不是 $O(maxSize)$ 。 |
| 175 | + |
| 176 | +每次栈 push 的时候,incrementals 也 push 一个 0。每次栈 pop 的时候, incrementals 也 pop,这样就可以了。 |
| 177 | + |
| 178 | +> 这里的 incrementals 并不是一个栈,而是一个普通数组,因此可以随机访问。 |
| 179 | +
|
| 180 | +### 代码 |
| 181 | + |
| 182 | +代码支持: Python3, Go, PHP |
| 183 | + |
| 184 | +Python3 Code: |
| 185 | + |
| 186 | +```py |
| 187 | +class CustomStack: |
| 188 | + |
| 189 | + def __init__(self, size: int): |
| 190 | + self.st = [] |
| 191 | + self.cnt = 0 |
| 192 | + self.size = size |
| 193 | + self.incrementals = [] |
| 194 | + |
| 195 | + def push(self, x: int) -> None: |
| 196 | + if self.cnt < self.size: |
| 197 | + self.st.append(x) |
| 198 | + self.incrementals.append(0) |
| 199 | + self.cnt += 1 |
| 200 | + |
| 201 | + |
| 202 | + def pop(self) -> int: |
| 203 | + if self.cnt == 0: return -1 |
| 204 | + self.cnt -= 1 |
| 205 | + if self.cnt >= 1: |
| 206 | + self.incrementals[-2] += self.incrementals[-1] |
| 207 | + return self.st.pop() + self.incrementals.pop() |
| 208 | + |
| 209 | + |
| 210 | + def increment(self, k: int, val: int) -> None: |
| 211 | + if self.incrementals: |
| 212 | + self.incrementals[min(self.cnt, k) - 1] += val |
| 213 | +``` |
| 214 | + |
| 215 | +Go Code: |
| 216 | + |
| 217 | +```go |
| 218 | +type CustomStack struct { |
| 219 | + Stack []int |
| 220 | + PreSum []int // 前缀和 |
| 221 | + Cnt int |
| 222 | + Size int |
| 223 | + Top int |
| 224 | +} |
| 225 | + |
| 226 | +func Constructor(maxSize int) CustomStack { |
| 227 | + // 因为 go 语言 slice 底层实现机制, 如果不设置 cap, 会导致频繁扩容, 可能增大时间/内存消耗 |
| 228 | + // 提交测试后的最佳组合: Stack 使用默认值, PreSum 预设为 MaxSize |
| 229 | + return CustomStack{Size: maxSize, PreSum: make([]int, maxSize, maxSize)} |
| 230 | +} |
| 231 | + |
| 232 | +func (this *CustomStack) Push(x int) { |
| 233 | + if this.Cnt < this.Size { |
| 234 | + this.Stack = append(this.Stack, x) |
| 235 | + this.Cnt++ |
| 236 | + } |
| 237 | +} |
| 238 | + |
| 239 | +func (this *CustomStack) Pop() int { |
| 240 | + if this.Cnt == 0 { |
| 241 | + return -1 |
| 242 | + } |
| 243 | + n := len(this.Stack) |
| 244 | + var a int |
| 245 | + a, this.Stack = this.Stack[n-1], this.Stack[:n-1] |
| 246 | + this.Top = a + this.PreSum[n-1] |
| 247 | + if this.Cnt>=2 { // 重置 |
| 248 | + this.PreSum[n-2] += this.PreSum[n-1] |
| 249 | + } |
| 250 | + this.PreSum[n-1] = 0 |
| 251 | + this.Cnt-- |
| 252 | + return this.Top |
| 253 | +} |
| 254 | + |
| 255 | +func (this *CustomStack) Increment(k int, val int) { |
| 256 | + if this.Cnt == 0 { |
| 257 | + return |
| 258 | + } |
| 259 | + n := min(k, this.Cnt) |
| 260 | + this.PreSum[n-1] += val |
| 261 | +} |
| 262 | + |
| 263 | +func min(a, b int) int { |
| 264 | + if a < b { |
| 265 | + return a |
| 266 | + } |
| 267 | + return b |
| 268 | +} |
| 269 | +``` |
| 270 | + |
| 271 | +PHP Code: |
| 272 | + |
| 273 | +```php |
| 274 | +class customStack |
| 275 | +{ |
| 276 | + public $stack = []; |
| 277 | + public $preSum = []; |
| 278 | + public $size; |
| 279 | + public $cnt = 0; |
| 280 | + public $top = -1; // debug |
| 281 | + |
| 282 | + /** |
| 283 | + * @param Integer $maxSize |
| 284 | + */ |
| 285 | + function __construct($maxSize) |
| 286 | + { |
| 287 | + $this->size = $maxSize; |
| 288 | + } |
| 289 | + |
| 290 | + /** |
| 291 | + * @param Integer $x |
| 292 | + * @return NULL |
| 293 | + */ |
| 294 | + function push($x) |
| 295 | + { |
| 296 | + if ($this->cnt < $this->size) { |
| 297 | + array_push($this->stack, $x); |
| 298 | + array_push($this->preSum, 0); |
| 299 | + $this->cnt++; |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + /** |
| 304 | + * @return Integer |
| 305 | + */ |
| 306 | + function pop() |
| 307 | + { |
| 308 | + if (!$this->cnt) return -1; |
| 309 | + if ($this->cnt >= 2) $this->preSum[$this->cnt - 2] += $this->preSum[$this->cnt - 1]; |
| 310 | + $this->cnt--; |
| 311 | + $this->top = array_pop($this->stack) + array_pop($this->preSum); |
| 312 | + return $this->top; |
| 313 | + } |
| 314 | + |
| 315 | + /** |
| 316 | + * @param Integer $k |
| 317 | + * @param Integer $val |
| 318 | + * @return NULL |
| 319 | + */ |
| 320 | + function increment($k, $val) |
| 321 | + { |
| 322 | + $k = min($k, $this->cnt); |
| 323 | + if ($k) $this->preSum[$k - 1] += $val; |
| 324 | + } |
| 325 | +} |
| 326 | +``` |
| 327 | + |
| 328 | +**复杂度分析** |
| 329 | + |
| 330 | +- 时间复杂度:全部都是 $O(1)$ |
| 331 | +- 空间复杂度:我们维护了一个大小为 cnt 的数组,因此平均到每次的空间复杂度为 $O(cnt / N)$,其中 N 为操作数,cnt 为操作过程中的栈的最大长度(小于等于 maxSize)。 |
| 332 | + |
| 333 | +可以看出优化的解法在 maxSize 非常大的时候是很有意义的。 |
| 334 | + |
| 335 | +## 相关题目 |
| 336 | + |
| 337 | +- [155. 最小栈](https://leetcode-cn.com/problems/min-stack/solution/chai-zhi-fa-155-zui-xiao-zhan-by-fe-lucifer/) |
| 338 | + |
| 339 | +更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。 |
| 340 | + |
| 341 | +大家也可以关注我的公众号《力扣加加》获取更多更新鲜的 LeetCode 题解 |
| 342 | + |
| 343 | + |
0 commit comments