二刷力扣--栈和队列

简介: 二刷力扣--栈和队列

栈和队列

栈和队列基础(Python)

栈一种先进后出,队列先进后出。

Python中可以用list实现栈,用append()模拟入栈,用pop()模拟出栈。

也可以用list实现队列,但是效率较低,一般用collections.deque模拟(双端)队列。

5. 数据结构 — Python 3.11.5 文档

使用list进行栈的操作

stack = [3, 4, 5]
stack.append(6)
stack.append(7)
stack
[3, 4, 5, 6, 7]
stack.pop()
7
stack
[3, 4, 5, 6]

使用collections.dequez进行队列操作

from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry")           # Terry arrives
queue.append("Graham")          # Graham arrives
queue.popleft()                 # The first to arrive now leaves
'Eric'
queue.popleft()                 # The second to arrive now leaves
'John'
queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

232.用栈实现队列

#模拟

请你仅使用两个栈实现先入先出队列。

思路:

使用两个栈stackin和stackout分别进行入队和出队。

出队时,如果stackout为空,将stackin 倒入 stackout。

class MyQueue:

    def __init__(self):
        self.stackin  = []
        self.stackout = []


    def push(self, x: int) -> None:
        self.stackin.append(x)


    def pop(self) -> int:
        if self.empty():
            return None
        if not self.stackout:
            while self.stackin:
                self.stackout.append(self.stackin.pop())
        return self.stackout.pop()


    def peek(self) -> int:
        res = self.pop()
        self.stackout.append(res)
        return res


    def empty(self) -> bool:
        return not (self.stackin or self.stackout)

225. 用队列实现栈

#模拟

重点还是pop。使用队列弹出元素时,需要先把之前的n-1个元素弹出,才能弹出第n个元素。

所以这里先弹出n-1个元素并添加到队列末尾,然后第n个元素就到了队列的开头。

这题没法仿照232的思路。注意队列和栈的区别,栈是反转元素进出顺序的,两次反转则变为先进先出。而队列是保持顺序的,进出两次队列后顺序不变。

class MyStack:

    def __init__(self):
        self.que = deque()

    def push(self, x: int) -> None:
        self.que.append(x)

    def pop(self) -> int:
        if self.empty():
            return None
        for i in range(len(self.que)-1):
            self.que.append(self.que.popleft())
        return self.que.popleft()

    def top(self) -> int:
        if self.empty():
            return None
        return self.que[-1]

    def empty(self) -> bool:
        return not self.que

20. 有效的括号

#栈

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串的括号是否有效。

栈的应用,使用栈来存储左括号,遇到右括号则弹出左括号进行匹配。

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        d_k = {'(': ')', '{': '}', '[': ']'}
        for c in s:
            if c in  d_k.keys():
                stack.append(c)
            else:
                if len(stack) == 0:
                    return False
                left = stack.pop()
                if c != d_k[left]:
                    return False
        
        return  len(stack) == 0

1047. 删除字符串中的所有相邻重复项

删除字符串的相邻重复字母,可以重复多次,直到没有没有相邻重复项。

#模拟 #栈

class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = []
        for c in s:
            if len(stack) > 0 and stack[-1] == c:
                stack.pop()
            else:
                stack.append(c)
        return "".join(stack)

150. 逆波兰表达式求值

#栈

逆波兰表达式又叫后缀表达式,运算符在操作数的后面,如:1 2 +

我们一般写的是中缀表达式,运算符在操作数的中间,如 : 1 + 2

输入: tokens = [“2”,“1”,“+”,“3”,“*”]

输出: 9

解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

借助栈,比较容易计算后缀表达式,遇到操作数就入栈,遇到运算符就弹出前面两个数,然后计算,并将计算结果入栈。

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        res = 0
        for i in tokens:
            if i == '+' :
                a = stack.pop()
                b = stack.pop()
                stack.append(b+a)
            elif i == '-' :
                a = stack.pop()
                b = stack.pop()
                stack.append(b-a)
            elif i == '*' :
                a = stack.pop()
                b = stack.pop()
                stack.append(b*a)
            elif i == '/':
                a = stack.pop()
                b = stack.pop()
                stack.append(int(b/a))
            else :
                stack.append(int(i))
        return int(stack[-1])

看起来有点重复,可以简化一下:

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        res = 0
        op_map = {'+': add, '-': sub, '*': mul, '/': lambda x, y: int(x / y)}

        for i in tokens:
            if i in op_map.keys():
                a = stack.pop() # 后
                b = stack.pop() # 前
                stack.append(op_map[i](b,a)) # 注意顺序 b op a
            else :
                stack.append(int(i))
        return int(stack[-1])


239. 滑动窗口最大值

#单调队列 #队列

整体思路:希望有一个队列,在滑动窗口的时候,用push添加新的元素,用pop弹出被删除的元素,并且能直接获取队列的最大元素。

也就是说,我们希望实现一个队列存储可能成为窗口中最大值的元素。(称为单调队列)

  1. 为了方便添加和删除元素,使用双端队列存储数据。
    self.queue = deque()
  1. 添加操作 push: 添加元素value时,如果队列末尾比value小,就pop掉,然后添加value。(也就意味着,队列中的元素都比新加入的value大。push操作很关键,保证了队列中的元素是单调递减的,因此后面可以用self.queue[0]获取最大值)
def push(self, value):
  while self.queue and value > self.queue[-1]:# 队列末尾比value小则删除
    self.queue.pop() # pop,弹出队列末尾元素
  self.queue.append(value)
  1. 删除操作 pop:删除元素value时,如果value等于队首元素que[0],则弹出队首popleft()
def pop(self, value):
  if self.queue and value == self.queue[0]:
    self.queue.popleft() # 弹出队首元素

获取最大值:

def front(self):
  return self.queue[0]

使用单调队列来解决滑动窗口最大值就比较简单了,不断地调用pop和push和 front。

class MyQueue:
    def __init__(self):
        self.queue = deque()
    # 删除value ,如果value 在队首则删除
    def pop(self, value):
        if self.queue and value == self.queue[0]:
            self.queue.popleft()
    
    # 添加value, valuea前面的比value小的都删除
    def push(self, value):
        while self.queue and value > self.queue[-1]:
            self.queue.pop()
        self.queue.append(value)
        
    def front(self):
        return self.queue[0]

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = MyQueue()
        res = []
        for i in range(k):
            que.push(nums[i])
        res.append(que.front())
        for i in range(k, len(nums)):
            que.pop(nums[i-k])
            que.push(nums[i])
            res.append(que.front())
        return res

347.前 K 个高频元素

#优先级队列 #堆

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。

最容易想到的是用字典统计频率然后排序。

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        mmap =  Counter(nums)
        sort = sorted(zip(mmap.values(),mmap.keys()), reverse=True)

        res = []
        for i in range(k):
            res.append(sort[i][1])
        return res

用堆排序,我们只需要维护一个大小为k的堆(优先级队列)。

在Python中可以用heapq或queue.PriorityQueue 实现。

heapq — 堆队列算法 — Python 3.11.5 文档

queue — 一个同步的队列类 — Python 3.11.5 文档

使用heapq

import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #要统计元素出现频率
        map_ = Counter(nums)
        
        #对频率排序
        #定义一个小顶堆,大小为k
        pri_que = [] #小顶堆
        
        #用固定大小为k的小顶堆,扫描所有频率的数值
        for key, freq in map_.items():
            heapq.heappush(pri_que, (freq, key))
            if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                heapq.heappop(pri_que)
        
        #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        result = [0] * k
        for i in range(k-1, -1, -1):
            result[i] = heapq.heappop(pri_que)[1]
        return result

使用PriorityQueue

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #要统计元素出现频率
        map_ = Counter(nums)
        
        #对频率排序
        from queue import PriorityQueue as PQ
        pri_que = PQ(k+1) #优先级队列(相当于小根堆), 最多放k+1个元素
        
        #用固定大小为k的小顶堆,扫描所有频率的数值
        for key, freq in map_.items():
            pri_que.put((freq, key))
            if pri_que.qsize() > k:
                pri_que.get()
        print(pri_que.queue)
        #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        result = [0] * k
        for i in range(k-1, -1, -1):
            result[i] = pri_que.get()[1]
        return result


栈和队列小结

栈主要用来处理相邻匹配问题,队列可以处理滑动窗口的最值问题(单调队列,前k个最值问题(优先级队列/小根堆)。

python中栈可以用list实现,队列用colelctions.deque实现。

stack = [3, 4, 5]
stack.append(6)
stack.append(7)
stack.pop()
import collections
queue = collections.deque()
queue.append(1) # 入队
queue.append(2)
queue.popleft() # 出队
import heapq
pri_que = [] #小顶堆
heapq.heappush(pri_que, 1) # 入队
heapq.heappush(pri_que, 2)
heapq.heappop(pri_que) # 出队
相关文章
|
5月前
|
存储 算法 测试技术
力扣经典150题第五十四题:最小栈
力扣经典150题第五十四题:最小栈
44 0
|
6月前
|
存储 算法 索引
力扣每日一题 6/24 模拟 数组 单调栈
力扣每日一题 6/24 模拟 数组 单调栈
39 0
|
2月前
【LeetCode 24】225.用队列实现栈
【LeetCode 24】225.用队列实现栈
12 0
|
2月前
|
算法
【LeetCode 23】232.用栈实现队列
【LeetCode 23】232.用栈实现队列
25 0
|
4月前
|
Python
【Leetcode刷题Python】946. 验证栈序列
LeetCode题目“946. 验证栈序列”的Python解决方案,通过模拟栈的压入和弹出操作来验证给定的两个序列是否能通过合法的栈操作得到。
33 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
32 4
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 09. 用两个栈实现队列
使用两个栈实现队列的Python解决方案,包括初始化两个栈、实现在队列尾部添加整数的appendTail方法和在队列头部删除整数的deleteHead方法,以及相应的示例操作。
41 2
|
4月前
|
Python
【Leetcode刷题Python】641.循环双端队列
文章介绍了如何实现一个循环双端队列,包括其操作如插入、删除、获取队首和队尾元素,以及检查队列是否为空或已满,并提供了Python语言的实现代码。
25 0
|
4月前
|
Python
【Leetcode刷题Python】232. 用栈实现队列
如何使用Python语言通过两个栈来实现队列的所有基本操作,包括入队(push)、出队(pop)、查看队首元素(peek)和判断队列是否为空(empty),并提供了相应的代码实现。
22 0
|
6月前
|
算法
二刷力扣--二叉树(3)
二刷力扣--二叉树(3)