python算法刷题——栈和队列(一)

算法菜鸡的刷题记录,写的代码可能比较多冗余,可以到leetcode解题区看更多大佬们优雅的解题~

一、栈和队列

栈(stack): 后进先出。
栈的一些标准操作:

s.pop()		# 出栈
s.push()	# 入栈
s.top()		# 获取栈顶元素(不出栈)
s.size()	# 获取栈的大小(元素个数)
s.empty()	# 判断栈是否为空,返回true or false

队列(queue): 先进先出。
队列的一些标准操作:

q.pop()		# 出队
q.push()	# 入队
q.front()	# 获取队首元素(出队一端)
q.back()	# 获取队尾元素(入队一端)
q.size()	# 获取队列大小
q.empty()	# 判断队列是否为空,返回true or false

python中的栈和队列的一些标准操作:

from Queue import Queue, LifoQueue

q = Queue(maxsize=x)	# Queue,一般队列,maxsize--指定队列大小,可选参数
lq = LifoQueue(maxsize=x)	#LifoQueue,后进先出队列,相当于栈

"""只用q举例,lq方法同"""
q.put(x)	# 入队
q.get()		# 出队,会返回出队元素
q.qsize()	# 获取当前队列元素个数
q.full()	# 判断队列是否已满
q.empty()	# 判断队列是否为空

python中也可以用list来实现栈

stack = []
stack.append()	# 相当于元素入栈
stack.pop()		# 相当于元素出栈

经实践,直接用list来实现栈相比于LifoQueue效率会更高(leetcode上是实践结果这样…没看源码实现…如果有误请多指教。)

二、实战

1. leetcode 225 用队列实现栈

在这里插入图片描述
思考点:

  • 可以修改队列的入队方式,使得队列入队之后结构与栈相同,其余操作就可以直接进行。
  • 也可以修改队列的出队方式,使得队列的出队顺序与栈相同。

以修改入队方式为例,如何修改?

  • 临时队列的应用
from queue import Queue
class MyStack:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.data_queue = Queue()	# data_queue用于实际存储数据
        self.tmp_queue = Queue()	# tmp_queue作为临时队列使用

	"""
	1. 每次入队之前,先将元素push到tmp_queue中,此时该元素处于队首
	2. 将data_queue的元素依次push到tmp_queue中(假设此时data_queue的元素已经按照栈的结构存储)
	3. 再将tmp_queue的元素复制到data_queue中(data_queue是实际用来作为存储的元素)
	"""

    def push(self, x: int) -> None:
        """
        Push element x onto stack.
        """
        self.tmp_queue.put(x)
        while not self.data_queue.empty():
            self.tmp_queue.put(self.data_queue.get())
        while not self.tmp_queue.empty():
            self.data_queue.put(self.tmp_queue.get())


    def pop(self) -> int:
        """
        Removes the element on top of the stack and returns that element.
        """
        return self.data_queue.get()

	"""这里要注意queue中没有直接的top方法,get()方法会将元素弹出,需要重新压入"""
    def top(self) -> int:
        """
        Get the top element.
        """
        x = self.data_queue.get()
        self.push(x)
        return x


    def empty(self) -> bool:
        """
        Returns whether the stack is empty.
        """
        return self.data_queue.empty()

时间复杂度:push操作为O(n),其余为O(1)
空间复杂度:O(n)

  • 当然其实可以用一个队列也能完成,思路也简单明了,当我们要入栈时,将元素x push到队列中,同时将位于x前面的元素出队再入队,这样x就变成了队首元素。具体实现如下(主要是修改了Push方法,其余与上面代码一致):
    def push(self, x: int) -> None:
        """
        Push element x onto stack.
        """
        tmp = self.data_queue.qsize()
        self.data_queue.put(x)
        while tmp>0:
            self.data_queue.put(self.data_queue.get())
            tmp -= 1
2. leetcode 232 使用栈实现队列

在这里插入图片描述
这题与上题原理和思考方式都类似,我们可以参照着来实现。(这里使用了LifoQueue,当然也可以直接用list实现栈)

  • 使用临时栈
from queue import LifoQueue
class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.data_stack = LifoQueue()
        self.tmp_stack = LifoQueue()

	"""
	1. 先将data_stack中的元素依次push到tmp_stack中(data_Stack是已经按照队列的顺序存储的数据栈,push到tmp_stack后tmp_stack得到的是一个与data_stack相反的栈)
	2. 将元素x压入data_stack中,此时相当于队尾元素放在栈底
	3. 将tmp_stack中的元素重新压入data_stack中
	"""
    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        while not self.data_stack.empty():
            self.tmp_stack.put(self.data_stack.get())
        self.data_stack.put(x)
        while not self.tmp_stack.empty():
            self.data_stack.put(self.tmp_stack.get())


    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        return self.data_stack.get()

	"""这里注意与上一题不同,栈是后进先出的,所以只需要直接调用put方法将其放回栈顶即可,不要调用push将其放入栈底!!!"""
    def peek(self) -> int:
        """
        Get the front element.
        """
        x = self.data_stack.get()
        self.data_stack.put(x)
        return x

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        return self.data_stack.empty()

时间复杂度:入队O(n),出队O(1)
空间复杂度:O(n)

另一种同样是使用了两个栈,但是能够实现入队和出队时间复杂度都达到O(1)的方法,如下:

from queue import LifoQueue
class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.push_stack = LifoQueue()
        self.pop_stack = LifoQueue()

    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        self.push_stack.put(x)

	"""
	1.当pop_stack为空时,一次性将所有的push_stack中的元素压入pop_stack中,此时pop_stack中的元素与实际队列的存储顺序是一致的
	2.弹出pop_stack栈顶元素即可
	"""
    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if self.pop_stack.empty():
            while not self.push_stack.empty():
                self.pop_stack.put(self.push_stack.get())
        return self.pop_stack.get()


    def peek(self) -> int:
        """
        Get the front element.
        """
        if self.pop_stack.empty():
            while not self.push_stack.empty():
                self.pop_stack.put(self.push_stack.get())
        x = self.pop_stack.get()
        self.pop_stack.put(x)
        return x

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        return self.push_stack.empty() and self.pop_stack.empty()

此时入队和出队操作的时间复杂度都是O(1),为什么出队也是O(1)呢?这里涉及到摊还思想 ,简单理解就是,每一个元素都入栈两次、出栈两次(push_stack和pop_stack),所以平均的复杂度是O(1)。更具体的理解参考官方题解

3.leetcode155 最小栈

在这里插入图片描述
这个题目有个注意的点是在常数时间内 检索到。遍历栈或者用一些排序方式固然可以找到最小值,但是无法在满足常数时间的要求。
要在常数时间内实现,我们会想到,如果有一个变量来记录最小值,需要的时候直接取到就可以实现O(1)。
这里借助一个辅助栈,这个栈要记录什么呢?由于入栈出栈操作是动态的,所以最小值也是动态的,我们可以用一个栈来维护每一个状态下的最小值。具体实现如下:

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.data_stack = []
        self.min_stack = []

    """
	1. 当第一个元素入栈时,它就是当前栈的最小值,于是Push到min_stack
	2. 当入栈元素小于min_stack的栈顶元素时,说明该元素入栈之后是当前状态的最小值,因此将它push到min_stack中
	3. 当入栈元素大于min_stack的栈顶元素时,说明该元素入栈之后当前状态的最小值没有发生改变,因此将原来的最小值(就是min_stack栈顶元素)push到min_stack中
	"""
    def push(self, x: int) -> None:
        self.data_stack.append(x)
        if self.min_stack == [] or self.min_stack[-1] > x:
            self.min_stack.append(x)
        else:
            self.min_stack.append(self.min_stack[-1])

    def pop(self) -> None:
        self.data_stack = self.data_stack[0:-1]
        self.min_stack = self.min_stack[0:-1]

    def top(self) -> int:
        return self.data_stack[-1]

    def getMin(self) -> int:
        return self.min_stack[-1]
4. leetcode946 验证栈序列

在这里插入图片描述
这个题目简单说就是验证出栈顺序(poped)是否正确,那很自然想到可以模拟一个它的出栈入栈顺序来验证。具体来看实现:

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        if not pushed and not popped:
            return True
        tmp_stack = []	# 用于模拟入栈
        """
		1. 遍历pushed,模拟入栈
		2. 在入栈过程中,如果栈顶元素与poped的第一个元素相同,则该元素从tmp_stack中出栈,同样从pooped中去除(或者采用指针移到下一个位置的方式),否则下一个元素入栈
		3. 如果最后poped == [](或者指针将要越界),说明序列合法
		"""
        for i in pushed:
            tmp_stack.append(i)
            while tmp_stack and tmp_stack[-1] == popped[0]:
                tmp_stack = tmp_stack[0:-1]
                popped = popped[1:]
                if not popped:
                    return True
        return False

时间复杂度O(n)(遍历pushed),空间复杂度O(n)

5.leetcode224 基本计算器

这题不是非常理解写的非常乱……建议跳过……
在这里插入图片描述
这是一个hard题,在官方题解和一些讲解中的代码看的迷迷糊糊,要自己实现还有点困难,这里先说一下思路,然后附上我傻瓜式的实现。后面再来补充一些更好的方法。
思路比较简单,没什么技巧,就是硬解(-^-|||)
遍历字符串
1.如果是数字,加入num_stack中;
2.如果是+/-,加入op_stack中,设置compute=True;
3.如果是左括号,说明现在还不能进行计算(优先级的问题),设置compute=False;
4.如果是右括号,说明括号里的内容遍历完了,可以进行运算了,设置compute=True;

什么时候进行运算:
1.compute必须为true;
2.满足运算规则(就是比如加减法,两个数字对应一个运算符才能进行运算)

如何进行运算:
1.栈顶的符号相当于当前状态时最内层的运算符,所以取栈顶符号进行运算
2.数字是从左到右入栈的,计算时应该从左到右运算;最内层的数字最后入栈,所以也是栈顶开始运算

class Solution:
    def calculate(self, s):
        num_stack = []	#记录数据
        op_stack = []	#记录运算符
        num = 0
        res = 0
        compute = False		# 用于标记现在是否可以运算
        i = 0
        while i < len(s):
			"""
			这里可以想成如果len(num_stack)==len(op_stack)的话说明先前有括号/或者下一个数字还未入栈,不能进行运算
			"""
            if len(num_stack) > len(op_stack) and compute and op_stack:
                if op_stack[-1] == '+':
                    res = num_stack[-2] + num_stack[-1]
                elif op_stack[-1] == '-':
                    res = num_stack[-2] - num_stack[-1]
                num_stack = num_stack[0:-2]
                num_stack.append(res)
                op_stack = op_stack[0:-1]

            if '0' <= s[i] <= '9':
                while i < len(s) and '0' <= s[i] <= '9':
                    num = (num * 10) + int(s[i])
                    i = i + 1
                num_stack.append(num)
                num = 0
                continue

            elif s[i] == '+' or s[i] == '-':
                op_stack.append(s[i])
                compute = True

            elif s[i] == '(':
                compute = False

            else:
                compute = True
            i = i + 1

        while op_stack:
            if op_stack[-1] == '+':
                res = num_stack[-2] + num_stack[-1]
            elif op_stack[-1] == '-':
                res = num_stack[-2] - num_stack[-1]
            num_stack = num_stack[0:-2]
            num_stack.append(res)
            op_stack = op_stack[0:-1]

 
        return num_stack[-1]

视频(b站的leetcode算法讲解)里就讲了这几题,太长了下一篇再来记录一下栈和队列的其他题目~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值