如何理解栈?
关于”栈”,就像是一摞叠在一起的书籍。当我们放书籍的时候,都是一本本书的摞起来;当我们需要取书籍的时候,也是需要从上到下
一个个的依次的取。
所谓的栈:就是后进者先出,先进者后出,这就是典型的 栈 的结构。
从栈的操作特性上来看,栈是一种”操作受限”的线性表,只允许在一端插入和删除数据。
从功能上来说,数组和链表确实可以替代栈,但是特定的数据结构是对特定场景的抽象,而且,数组或者链表暴露了太多的操作接口,操作上的灵活,
在使用时也就会变得不可控,自然也就更容易出错。
当某个数据集合只涉及在一端插入和删除数据的时候,并且满足后进先出、先进先出的特性,这时我们就应该首选”栈”这种数据结构
如何实现一个”栈”?
栈可以使用数组来实现,也可以使用链表来实现;
这里我使用数组来实现
1 | class Stack(object): |
先说结论:
栈的空间复杂度为 O(1), 时间复杂度为 O(1)。
这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。我们在判断一个空间复杂度的时候,是指除了原本的数据存储空间外,
算法运行还需要额外的存储空间。
对于时间复杂度来说,入栈和出栈都是 O(1)
栈在函数调用中的应用
函数调用栈
栈作为一个比较基础的数据结构,比较经典的应用场景就是函数调用栈
。
操作系统给每个线程分配了一块独立的内存空间
,这块内存被组织成栈
这种数据结构,用来存储函数调用时的临时变量。
1 | def main(): |
调用顺序:main() –> add()
表达式求值
四则运算的实现
例如:16+26*36-2/2
实现思路:
- 维护一个栈,然后
实际上,编译器就是通过两个栈来实现的。其中一个来保存操作数的栈,另一个保存运算符的栈。
基本运算器实现
1 | def calculate(s: str) -> int: |
浏览器前进和后退功能实现
解题思路:使用两个栈,A 和 B,将访问的页面一次压入栈 A 中,当点击后退按钮时,再依次从栈 A 中出栈,并将出栈的数据依次放入栈 B 中。点击前进按钮时,依次从栈 B
中取出数据,放入到 A 中。当栈 A 中没有数据时,那就是说明没有页面可以继续后退浏览了。当栈 B 中没有数据的时候,那就说明没有页面可以点击前进按钮浏览了。
实现
1 | class Stack(object): |
总结
栈是一种操作受限的数据结构,只支持入栈
和出栈
操作。
可以通过数组
或者链表
来实现。
入栈、出栈的时间复杂度都是 O(1)。
问题
为什么函数调用要用栈来保存临时变量呢?
函数调用时,符合后进先出
的特性,用栈这种数据结构来实现,是最符合的。
从调用函数进入被调用函数,比如:main –> add –> res 。变化的是作用域
。所以从根本上,只要保证每进入一个新的函数,都是一个新的作用域都可以。
而要实现这个,用栈会非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。
在 JVM 内存管理中有个堆栈
的概念 ?
内存中的堆栈和数据结构堆栈不是同一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。
内存空间在逻辑上分为三部分:
- 代码区
- 静态数据区
- 动态数据区
- 栈区
- 堆区
代码区:存储方法体的二进制代码
。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
静态数据区:存储全局变量
、静态变量
、常量
、常量包括 final 修饰的常量和 String 常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收
堆区:new 一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。
相关题目
有效的括号
20 - https://leetcode-cn.com/problems/valid-parentheses/
解题思路:
- 括号是成对出现的,则判断字符串 s 是否为偶数,否则返回 False;
- 遍历字符串,先将 左括号压入栈,匹配到右括号时,则判断栈顶是否匹配到,否则返回 False;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25def isValid_bracket(brackets: str) -> bool:
if len(brackets) % 2 == 1:
return False
pairs = {
')': '(',
']': '[',
'}': '{'
}
stack = Stack()
for ch in brackets:
if ch in pairs:
if not stack or stack.top() != pairs[ch]:
return False
stack.pull()
else:
stack.push(ch)
return stack.is_empty()
if __name__ == '__main__':
print(isValid_bracket('([{}])'))最小栈
解题思路:维护一个辅助栈,在入栈的时候,依次与辅助栈中的数据比较,采用min(val, min_stack[-1])
的方式入栈。pop 时,min_stack 也会 pop
155 - https://leetcode-cn.com/problems/min-stack/1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class MinStack:
def __init__(self):
self._stack = []
self.min_stack = [math.inf] # 存储正无穷大
def push(self, val: int) -> None:
self._stack.append(val)
self.min_stack.append(min(val, self.min_stack[-1]))
def pop(self) -> None:
self._stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self._stack[-1]
def getMin(self) -> float:
return self.min_stack[-1]用两个栈实现队列
232 - https://leetcode-cn.com/problems/implement-queue-using-stacks/1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self.s1 = []
self.s2 = []
self.front = None
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
"""
if not self.s1:
self.front = x
self.s1.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if not self.s2:
while self.s1:
self.s2.append(self.s1.pop())
self.front = None
return self.s2.pop()
def peek(self) -> int:
"""
Get the front element.
"""
if self.s2:
return self.s2[-1]
return self.front
def empty(self) -> bool:
"""
Returns whether the queue is empty.
"""
if not self.s1 and not self.s2:
return True
return False比较含退格的字符串
844 - https://leetcode-cn.com/problems/backspace-string-compare/1
2
3
4
5
6
7
8
9
10
11
12
13class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
def judge(s: str) -> str:
ret = []
for value in s:
if value != '#':
ret.append(value)
elif ret:
ret.pop()
return ''.join(ret)
return judge(s) == judge(t)
- 遍历字符串,先将 左括号压入栈,匹配到右括号时,则判断栈顶是否匹配到,否则返回 False;
基本计算器实现
224 - https://leetcode-cn.com/problems/basic-calculator/
1 | def calculate(s): |
基本运算器 3
https://leetcode-cn.com/problems/basic-calculator-iii/
基本运算元素:
(
)
-
+
*
/
运算符优先级:
- 2
/ *
- 1
+ -
- 0
( )
解题思路:
双栈法
- stack_num 存储数字
- stack_opt 存储运算符
最小原子计算:
- stack_num 中 pop 出两个数 A, B
- stack_opt 中 pop 出一个操作符 opt
- 计算结果:res = B opt A
- 存储运算结果:stack_num.append(res)
字符串枚举:
空格
,continue 下一个循环数字
- 单个数字
stack_num.append()
- 多位数字
解析多位数字,append
- ‘(‘
直接进入 stack_opt 栈 - ‘)’
重复最小原子计算,直到stack_opt
栈顶为(
。然后 stack_opt 栈 pop - 操作符(+,-,*,/)
比较当前操作符
与栈顶操作符
优先级,若前者大于后者,则进行最小原子计算操作。
反之,则压入stack_opt
栈中。
3.遍历完所有字符串后,判断 stack_opt
是否为空,重复执行最基本计算操作,直到为空。
1 | # 基本运算器 |