Leetcode栈与队列

栈与队列基础知识与Python实现

定义

是一种后进先出的数据结构,元素从顶端入栈,然后从顶端出栈。
队列是一种先进先出的数据结构,元素从后端入队,然后从前端出队。

python实现

  1. python中用数组/链表实现栈(stack)
    对数组:添加要添在尾部,由于出栈是先拿数组尾部元素O(1),若添加在开头,出栈时间复杂度O(n)
    对链表:添加要添在头部,若添加在尾部,出栈时间复杂度O(n)
    常用数组操作:append() pop()
  2. 内置模块deque、queue 实现栈
    普通的queue.Queue或SimpleQueue没有类似于peek的功能,也无法用索引访问
    deque可以用索引访问,可以实现和peek相似的功能
    注:若用list实现队列(queue),pop时间复杂度O(n)

deque(double-end-queue双向队列)模块
近似O(1)的性能在deque的两端插入和删除元素

常用方法:
append():从右端添加元素(与list同)
appendleft():从左端添加元素
extend():从右端逐个添加可迭代对象(Python中的可迭代对象有:列表、元组、字典、字符串)(与list同)
extendleft():从左端逐个添加可迭代对象
pop():移除列表中的一个元素(默认最右端的一个元素),并且返回该元素的值(与list同),如果没有元素,将会报出IndexError
popleft():移除列表中的一个元素(默认最左端的一个元素),并且返回该元素的值,如果没有元素,将会报出IndexError
count():统计列表中指定元素出现的个数(与list同)
len():队列元素个数
insert(index,obj):在指定位置插入元素(与list同)
clear():将deque中的元素全部删除,最后长度为0

queue模块

queue 模块即一个整数,用于设置队列的最大长度。一旦队列达到上限,插入数据将会被阻塞,直到有数据出队列之后才可以继续插入。如果 maxsize 设置为小于或等于零,则队列的长度没有限制
2. queue.LifoQueue(maxsize=0)——后进先出(Last In First Out: LIFO),
3. queue.PriorityQueue(maxsize=0)
优先级队列,比较队列中每个数据的大小,值最小的数据拥有出队列的优先权(优先级的值越小,则表明优先级越高)(小顶堆)。数据一般以元组的形式插入,典型形式为**(priority_number, data)**。如果队列中的数据没有可比性,那么数据将被包装在一个类中,忽略数据值,仅仅比较优先级数字
优先队列是堆的常用场合

import queue
q = queue.PriorityQueue()
q.put([1, 'HaiCoder'])   # 1是级别  越小级别越高,优先出队
q.put([40, 1024])
q.put([3, 'Python'])
q.put([5, True])
if __name__ == '__main__':
    while not q.empty():  # 不为空时候执行
        print("get data =", q.get())
   
# put()/get()/empty()方法
输出:
get data = [1, 'HaiCoder']  # 1的优先级最高
get data = [3, 'Python']
get data = [5, True]
get data = [40, 1024]

使用heapq模块实现堆
是一种完全二叉树。只不过堆是二叉树顺序结构的实现,说白了就是将一个数组看作二叉树。也就是说,堆的逻辑结构是一棵二叉树,存储结构是数组

a = [] #创建一个空堆
heappush(heap,item) # 添加新值,此时自动建立了小根堆,heap是堆的意思, item是要被压入到heap中的对象
item可以为元组。 这适用于将比较值(例如任务优先级)与跟踪的主记录进行赋值的场合:

import heapq
h = []  # 空堆
heapq.heappush(h, (5, 'write code'))  # 5是优先级  
heapq.heappush(h, (7, 'release product'))
heapq.heappush(h, (1, 'write spec'))
heapq.heappush(h, (3, 'create tests'))
heapq.heappop(h), heapq.heappop(h)
输出:
(1, 'write spec'),(3, 'create tests')

heapq.heappop()是从堆中弹出并返回最小的值
在这里插入图片描述

import heapq
a = []   #创建一个空堆
heapq.heappush(a,18)
heapq.heappush(a,1)
heapq.heappush(a,20)
heapq.heappush(a,10)
heapq.heappush(a,5)
heapq.heappush(a,200)
print(a)
输出:[1, 5, 20, 18, 10, 200]

heapq无法直接建立大顶堆,但可以采取如下方法:每次push时给元素加一个负号(即取相反数),此时最小值变最大值,反之亦然,那么实际上的最大值就可以处于堆顶了,返回时再取负即可。
在这里插入图片描述

a = []
for i in [1, 5, 20, 18, 10, 200]:
    heapq.heappush(a,-i)
print(list(map(lambda x:-x,a)))
输出:[200, 18, 20, 1, 10, 5]
  1. queue.SimpleQueue
    先进先出类型的简单队列,没有大小限制。相比于 Queue 队列会缺少一些高级功能

Queue、LifoQueue、PriorityQueue 和 SimpleQueue 对象的基本使用方法
以 Queue 队列为例进行介绍(其他三个对象也有相同的方法)。

  1. Queue.qsize()
    返回队列中数据元素的个数。
import queue
q = queue.Queue()
q.put('python-100')  # 在队列中插入元素 'python-100'
print(q.qsize())  # 输出队列中元素个数为1
  1. Queue.empty()
    如果队列为空,返回 True,否则返回 False。
import queue
q = queue.Queue()
print(q.empty())  # 对列为空,返回 True
q.put('python-100')  # 在队列中插入元素 'python-100'
print(q.empty())  # 对列不为空,返回 False
  1. Queue.full()
    如果队列中元素个数达到上限,返回 True,否则返回 False。
import queue
q = queue.Queue(3)  # 定义一个长度为3的队列
print(q.full())  # 元素个数未达到上限,返回 False
q.put('python')  # 在队列中插入字符串 'python'
q.put('-') # 在队列中插入字符串 '-'
q.put('100') # 在队列中插入字符串 '100'
print(q.full())  # 元素个数达到上限,返回 True
  1. Queue.put(item, block=True, timeout=None)
    item,放入队列中的数据元素。
    block,当队列中元素个数达到上限继续往里放数据时:如果 block=False,直接引发 queue.Full 异常;如果 block=True,且 timeout=None,则一直等待直到有数据出队列后可以放入数据;如果 block=True,且 timeout=N,N 为某一正整数时,则等待 N 秒,如果队列中还没有位置放入数据就引发 queue.Full 异常。
    timeout,设置超时时间。
import queue
try:
    q = queue.Queue(2)  # 设置队列上限为2
    q.put('python')  # 在队列中插入字符串 'python'
    q.put('-') # 在队列中插入字符串 '-'
    q.put('100', block = True, timeout = 5) # 队列已满,继续在队列中插入字符串 '100',等待5秒后会引发 queue.Full 异常
except queue.Full:
    print('queue.Full')
  1. Queue.put_nowait(item)
    相当于 Queue.put(item, block=False),当队列中元素个数达到上限继续往里放数据时直接引发 queue.Full 异常。
import queue
try:
    q = queue.Queue(2)  # 设置队列上限为2
    q.put_nowait('python')  # 在队列中插入字符串 'python'
    q.put_nowait('-') # 在队列中插入字符串 '-'
    q.put_nowait('100') # 队列已满,继续在队列中插入字符串 '100',直接引发 queue.Full 异常
except queue.Full:
    print('queue.Full')
  1. Queue.get(block=True, timeout=None):从队列中取出数据并返回该数据内容。
    block,当队列中没有数据元素继续取数据时:如果 block=False,直接引发 queue.Empty 异常;如果 block=True,且 timeout=None,则一直等待直到有数据入队列后可以取出数据;如果 block=True,且 timeout=N,N 为某一正整数时,则等待 N 秒,如果队列中还没有数据放入的话就引发 queue.Empty 异常。
    timeout,设置超时时间。
import queue
try:
    q = queue.Queue()
    q.get(block = True, timeout = 5) # 队列为空,往队列中取数据时,等待5秒后会引发 queue.Empty 异常
except queue.Empty:
    print('queue.Empty')
  1. Queue.get_nowait()
    相当于 Queue.get(block=False)block,当队列中没有数据元素继续取数据时直接引发 queue.Empty 异常。
import queue
try:
    q = queue.Queue()
    q.get_nowait() # 队列为空,往队列中取数据时直接引发 queue.Empty 异常
except queue.Empty:
    print('queue.Empty')

Queue、LifoQueue 和 PriorityQueue 对象的高级使用方法
SimpleQueue 是 Python 3.7 版本中新加入的特性,与 Queue、LifoQueue 和 PriorityQueue 三种队列相比缺少了 task_done 和 join 的高级使用方法,所以才会取名叫 Simple 了,下面介绍一下 task_done 和 join 的使用方法。

task_done,表示队列内的数据元素已经被取出,即每个 get 用于获取一个数据元素, 后续调用 task_done 告诉队列,该数据的处理已经完成。如果被调用的次数多于放入队列中的元素个数,将引发 ValueError 异常。
join,一直阻塞直到队列中的所有数据元素都被取出和执行,只要有元素添加到 queue 中就会增加。当未完成任务的计数等于0,join 就不会阻塞。


232 用栈实现队列

在这里插入图片描述
分析:
一个做输入栈,一个做输出栈,实现先入先出
注意:当输出栈中无元素时才能将输入栈中元素写入

代码:

class MyQueue:

    def __init__(self):
        self.stack1 = []  # python 中用数组实现栈
        self.stack2 = []

    def push(self, x: int) -> None:
        self.stack1.append(x)
        # 时O(1)

    def pop(self) -> int:
        if self.stack2 == []:  # 当输出栈为空时,才能将输入栈中元素移入
            while self.stack1 != []:
                self.stack2.append(self.stack1.pop())  # 数组的append()和pop()操作
        return self.stack2.pop()

    def peek(self) -> int:
        if self.stack2 == []:
            while self.stack1 != []:
                self.stack2.append(self.stack1.pop())
        return self.stack2[-1]

    def empty(self) -> bool:
        if self.stack1 == [] and self.stack2 == []:
            return True
        return False

225 用队列实现栈

在这里插入图片描述
用两个栈:

class MyStack:
    # 两个队列
    def __init__(self):
        self.queue_in = deque()
        self.queue_out = deque()  # 用于暂存

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

    def pop(self) -> int:
        if self.empty():
            return None
        for i in range(len(self.queue_in)-1):
            self.queue_out.append(self.queue_in.popleft())  # popleft() 先进先出
        self.queue_in,self.queue_out = self.queue_out,self.queue_in
        return self.queue_out.popleft()

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

    def empty(self) -> bool:
        return len(self.queue_in)==0

只用一个栈:

class MyStack:
    # 一个队列
    def __init__(self):
        self.queue = deque()

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

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

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

    def empty(self) -> bool:
        return len(self.queue)==0  # return not self.queue

debug:def empty中判断是否为空不能用self.queue==[] !! deque()而非数组
注:使用deque而不用list是因为deque可以以近似O(1)的性能在deque的两端插入和删除元素。由于队列时先入先出,这里pop使用popleft()

20 有效的括号

在这里插入图片描述
分析:先出现的括号最后配对——先入后出——栈

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        for item in s:
            if item == '(':
                stack.append(')')
            elif item == '[':
                stack.append(']')
            elif item == '{':
                stack.append('}')
            elif stack == [] or stack[-1] != item:  # 两个条件先后很重要 否则会出现溢出
                return False  # 若当前item和栈顶不同 或 若还未遍历完但stack已空 均属于无效字符串
            else:
                stack.pop() 
        return stack == []  # 若遍历完但stack中还有 也无效

debug:
elif stack == [] or stack[-1] != item: # 两个条件先后很重要 否则会出现溢出


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

在这里插入图片描述
法1 使用栈
分析:将字符串写入栈 后入先出

  1. 若栈非空且写入字符和栈顶相同,则指向下一个字符,栈顶元素pop,指针重复len(s)次;
  2. 其余情况(栈为空或写入字符和栈顶不相同):压入当前字符 跳到下一个字符
  3. 时:O(n) 空:O(n)
class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = []
        for i in range(len(s)):
            if stack and stack[-1] == s[i]:
                stack.pop()
            else:
                stack.append(s[i])
        return ''.join(stack)

法2 使用双指针——# 时O(n) 空O(n)
注意s[i] = s[j]

class Solution:
    def removeDuplicates(self, s: str) -> str:
        if len(s)==1:
            return s
        else:
            i = j = 0  # i新列表索引   j原列表索引
            s = list(s)
            for j in range(len(s)):
                s[i] = s[j]  # !!! 
                if i>0 and s[i]==s[i-1]:  # 和前一个值相等——消去
                    i -= 1  # 当前新列表左移一位
                else:
                    i += 1
            return ''.join(s[:i])

150 逆波兰表达式求值

在这里插入图片描述
分析:
遍历原字符串,将数据入栈,遇到符号则pop出两个数据运算,并将结果入栈

向零取整即向0方向取最接近精确值的整数,换言之就是舍去小数部分,因此又称截断取整。
python字符串与数字转化:
字符串str转数字:
float(str)
int(str)
数字num转字符串:
str(num)

python取整——向上取整、向下取整、四舍五入取整、向0取整
向上取整:math.ceil()
向下取整:math.floor()、整除"//"
四舍五入:round()
向0取整:int() ——就是取整数部分

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        sym = {'+','-','*','/'}  # 使用集合 查找时间复杂度O(1)
        stack = []
        for token in tokens:
            if token in sym:
                token1 = stack.pop()
                token2 = stack.pop()
                if token == '+':
                    stack.append(token2+token1)  # stack.append(stack.pop()+stack.pop()) 
                elif token == '-':
                    stack.append(token2-token1)  # stack.append(-stack.pop()+stack.pop()) 
                elif token == '*':
                    stack.append(token2*token1)  # stack.append(stack.pop()*stack.pop())
                else:
                    stack.append(int(token2/token1))  # 向0取整
            else:  # 数字直接压入栈
                stack.append(int(token))  # 字符转整型数据
        return stack[0]

239 滑动窗口最大值

在这里插入图片描述
分析:
# 设置双端队列(两边都可以pop,便于O(1)时间获取)存储可能的最大值(降序)
# 若新添加的元素小于最小值,则直接添加在末尾。
# 若大于或等于最小值,则将队尾元素pop(因为此元素已不可能是一个滑动窗中的最大值了,无需维护),继续与队尾比较,直至小于队尾或为空。
# 每次输出最大值前,要检查队首(即最大值索引)是否在滑动窗中,若不在,则pop,然后输出队首。
# 队列中保存的是降序排列的元素的索引,滑动窗左索引为l,右索引记为r,很明显r-l+1<=k(l>=r-k+1),r同时作为遍历数组的指针,若队首元素<=r-k,说明队首元素不在滑动窗中
# 时O(n)(nums 中的每个元素最多也就被 push和 pop 各一次,没有任何多余操作,所以整体的复杂度还是 O(n))
# 空O(k)(辅助队列)

小结:
设计单调队列的时候,pop,和push操作要保持如下规则:

pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = collections.deque()
        res = []
        for i in range(len(nums)):  # i同时是滑动窗口的右端
            while que and nums[i]>=nums[que[-1]]:  # 若当前元素大于队尾  则将队尾出栈
                que.pop()
            que.append(i)
            if que[0]<=i-k:  # 判断队首元素是否还在滑动窗口中
                que.popleft()
            if i+1>=k:  # +1 由于python从0开始  此处意为遍历的元素数目大于等于窗口大小  则开始输出最大值
                res.append(nums[que[0]])
        return res
    # debug 判断队列deque()是否为空不能用None 

347 前K个高频元素

在这里插入图片描述
优先级队列:一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列

而且优先级队列内部元素是自动依照元素的权值排列(有序)。缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

:堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

所以大家经常说的大顶堆(堆头是最大元素)小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆

总结:
优先级队列就是队首取元素,队尾添元素,内部排序的堆,升序就是小顶堆(队首小),降序就是大顶堆(队首大)。

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 统计次数
        hashmap = {}
        for i in range(len(nums)):
            hashmap[nums[i]] = hashmap.get(nums[i],0)+1  # .get(key,初值)
        
        # 用(小顶堆)
        minheap = []  #heap的数据类型只能是一个列表
        #用固定大小为k的小顶堆,扫描所有频率的数值
        for key,val in hashmap.items():  #字典  items()  返回可遍历的(键,值)元组数组
            heapq.heappush(minheap,(val,key))
            if len(minheap)>k:  #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                heapq.heappop(minheap)
        
        #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        res = [0]*k
        for i in range(k-1,-1,-1):
            res[i]=heapq.heappop(minheap)[1]  
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值