二刷力扣--栈和队列

栈和队列

栈和队列基础(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()
  2. 添加操作 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) # 出队

你可能感兴趣的:(LeetCode,python,leetcode,python,算法)