前言:本篇只代表本人对于栈的理解,如有错误,烦请大家指出!
栈是一种简单的线性数据结构,用来存放数据,其严格遵守了先进后出的原则,即first in last out,有关于栈的操作也十分简单,无非就是增删等。
这类简单的数据结构我们可以使用列表来实现相关的操作。
由于其先进后出的特性,不难想到利用列表相关的操作就可以解决掉一些问题,如append方法实现增加元素,pop方法实现删除元素,len方法实现统计栈内元素的个数,接下来我就用类来实现自己的一个栈以及相关的拓展功能。
class Stack:
def __init__(self,size = 0):
self.stack = [] # 创建栈,本质上是列表
self.size = size # 栈的大小,初始化为0
首先是利用私有方法init来初始化参数,在构建栈时,我们用列表去模拟,并将栈内元素的个数初始化为0。
一.增加元素功能
def push(self,element): # element是待插入元素
self.stack.append(element)
self.size+=1
没什么需要过多注意的,element为待插入的元素,插入后需要将size更新。
二.删除元素功能
学到了数据结构这一部分,我们写代码就要相当谨慎,尤其是对删除需要相当敏感,我们在做删除操作时都需要考虑到,有没有可以删除的元素,如果栈内为空,那还怎么删除?
因此我在这里设计了一个检查队内是否还有元素的查空功能。
def is_empty(self):
# return len(self.stack) == 0
return self.size == 0
# 两种写法都可以
有了这个函数,我们便可以将删除功能完善起来,分情况讨论即可。
def pop(self):
if not self.is_empty:
self.stack.pop(-1)
self.size -= 1
# 删除的写法还有很多种
# del self.stack[-1]
else:
print("栈内没有元素可以删除!")
raise IndexError
三.取栈顶元素功能
def get_top(self):
if not self.is_empty:
return self.stack[-1]
else:
raise IndexError
这些功能都十分简单,无需多说什么。
四.统计栈内元素个数功能
def count(self):
return self.size
刷题时间
栈的内容十分简单,巧妙利用列表的功能即可,接下来我为了巩固一下栈的知识,给出几道十分经典的有关于栈的例题:
①括号匹配问题:
class Solution:
def isValid(self, s: str) -> bool:
stack = []
if not s:
return True # 这是字符为空的情况
for key in s:
if (key == "[") or (key == "(") or (key == "{"):
stack.append(key)
else:
if len(stack) == 0: # 这是只有右括号
return False
top = stack.pop(-1)
if (key == "]" and top != "[") or (key == "}" and top != "{") or (key == ")" and top != "("):
return False # 这是不匹配
if len(stack) != 0:
return False # 这是还剩左括号在里面没匹配
else:
return True # 这是正确
这个问题没什么需要注意的,有几种特殊情况需要考虑,我已经标注在图上了。
②最小辅助栈问题
分析题目不难发现前四个功能都十分平常,利用基本的列表方法就可以实现,但是看到最后一个获取最小值且只能在常数时间内检索,这个问题就变得不一样了起来,因为纵观我们所有的方法(包括min函数和建堆,排序都做不到常数的时间复杂度),这时我们就需要考虑以空间换取时间,即再借助另外一个栈来存放最小值,这样便可以直接通过索引拿到最小值。
class MinStack:
def __init__(self):
self.temp=[]
self.stack = []
def push(self, val: int) -> None:
self.stack.append(val)
if not self.temp:
self.temp.append(val)
else:
if val <= self.temp[-1]:
self.temp.append(val)
return None
def pop(self) -> None:
is_min = self.stack.pop(-1)
if self.temp and is_min == self.temp[-1]:
self.temp.pop(-1)
return None
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.temp[-1]
在初始化时构建两个栈,temp栈用来取最小值。(保证temp栈顶元素最小)
在push功能中,若temp内没有元素,就将这个val追加进入temp,若temp内已经有元素,那便与其栈顶的元素比较,如果比栈顶的元素值还要小,就让新的元素为栈顶。
在pop功能中,如果删除的就是当前的最小值,那么在temp列表中,也需要将栈顶元素删除。
这样就保证的了temp的栈顶元素是最小的,且时间复杂度为O(1),但是空间复杂度就是O(N)。
③行星碰撞问题
这一题的关键在于边界的处理,首先我们必须搞清楚何时会碰撞何时肯定不会,正负代表着右左,
只有前一个为右且自身为左时会碰撞,若前一个为左且自身为右,他们永远不会相碰撞。
同时应该注意碰撞后,会有行星爆炸,爆炸后,我们需要重新判断前面的能否在有行星爆炸了之后,能否继续相碰,每一次的判断都需要注意边界,尤其是两个行星都爆炸了,需要考虑的情况较多,一方面是到顶了,一方面是向左越界了。
本题中continue的妙用值得思考,即爆炸后重新的判断,同时也带给我了一点启示,每一次的判断都需要注意边界的控制,若下标在增加我们需要注意是否会右边越界,若下标在减小我们需要注意是否会左边越界,因此我的建议是每一次有关于下标变化的判断都加上越界的判断,这样就万无一失了。
class Solution:
def asteroidCollision(self, asteroids: List[int]) -> List[int]:
i = len(asteroids)-1
while i > 0:
if i<=len(asteroids)-1 and asteroids[i-1]>0 and asteroids[i]<0:
if abs(asteroids[i-1]) > abs(asteroids[i]):
asteroids.pop(i)
continue
elif i<=len(asteroids)-1 and asteroids[i-1]+asteroids[i] == 0:
asteroids.pop(i)
asteroids.pop(i-1)
i = i-2
if i >= 0 and asteroids[i] > 0:
i += 1
continue
else:
asteroids.pop(i-1)
i-=1
elif i<=len(asteroids)-1 and asteroids[i-1]<0 and asteroids[i]>0:
i-=1
else:
i-=1
return asteroids