Skip to content

Commit fcc9e0b

Browse files
committed
Create 1381.design-a-stack-with-increment-operation.md
1 parent 0418f92 commit fcc9e0b

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
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+
![image](https://user-images.githubusercontent.com/12479470/83656933-c096d300-a5f2-11ea-8f50-64ced5aa62f2.png)
116+
117+
如图黄色部分是我们需要执行增加操作,我这里画了一个挡板分割,实际上这个挡板不存在。那么如何记录黄色部分的信息呢?我举个例子来说
118+
119+
比如:
120+
121+
- 调用了 increment(3, 2),就把 increment[3] 增加 2。
122+
- 继续调用 increment(2, 5),就把 increment[2] 增加 5。
123+
124+
![image](https://user-images.githubusercontent.com/12479470/83640207-6855d600-a5de-11ea-809e-bba303927707.png)
125+
126+
而当我们 pop 的时候:
127+
128+
- 只需要将栈顶元素**加上 increment[cnt - 1]** 即可, 其中 cnt 为栈当前的大小。
129+
- 另外,我们需要将 increment[cnt - 1] 更新到 increment[cnt - 2],并将 increment[cnt - 1] 重置为 0。
130+
131+
![image](https://user-images.githubusercontent.com/12479470/83640238-7146a780-a5de-11ea-8b81-81439353068f.png)
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+
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)

0 commit comments

Comments
 (0)