代码随想录算法训练营第九天 | 栈与队列系列所有

栈与队列理论基础

简单来讲:就是栈和队列都是不可迭代的,同时底层实现可以是list等,包装后实现对外的push和pop接口。

代码随想录的理论链接如下:
栈和队列理论基础

232 用栈实现队列

第一次接触相关代码,不知道怎么写,就直接看答案。

明白了,其中最重要就是pop操作,需要使用一个stack_out的栈,认真理解此段代码。

代码随想录的代码

class MyQueue:

    def __init__(self):
        """
        in主要负责push,out主要负责pop
        """
        self.stack_in = []
        self.stack_out = []


    def push(self, x: int) -> None:
        """
        有新元素进来,就往in里面push
        """
        self.stack_in.append(x)


    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if self.empty():
            return None
        
        if self.stack_out:
            return self.stack_out.pop()
        else:
            for i in range(len(self.stack_in)):
                self.stack_out.append(self.stack_in.pop())
            return self.stack_out.pop()


    def peek(self) -> int:
        """
        Get the front element.
        """
        ans = self.pop()
        self.stack_out.append(ans)
        return ans


    def empty(self) -> bool:
        """
        只要in或者out有元素,说明队列不为空
        """
        return not (self.stack_in or self.stack_out)

第二遍自己写的代码

class MyQueue:

    def __init__(self):
       self.pushin = []
       self.popout = []


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

    def pop(self) -> int:
        if self.popout == []:
            n = len(self.pushin)
            for i in range(n-1,-1,-1):
                self.popout.append(self.pushin.pop())

            return self.popout.pop()
        else :
            return self.popout.pop()


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

        self.popout.append(res)

        return res
        


    def empty(self) -> bool:
        if self.pushin == [] and self.popout == [] :
            return True
        else :
            return False

225 用队列实现栈

用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!(这一点一定要注意!!!)

队列模拟栈,其实一个队列就够了,先说一说两个队列来实现栈的思路。

队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。

所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。

但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!

不会写,还是学习卡哥的代码

第一遍看不太懂,对Python各种数据结构的特性以及内置函数不了解,而且这种利用队列实现栈的思路也是之前没有接触过的。

代码随想录的代码

1、使用两个队列实现

from collections import deque

class MyStack:

    def __init__(self):
        """
        Python普通的Queue或SimpleQueue没有类似于peek的功能
        也无法用索引访问,在实现top的时候较为困难。

        用list可以,但是在使用pop(0)的时候时间复杂度为O(n)
        因此这里使用双向队列,我们保证只执行popleft()和append(),因为deque可以用索引访问,可以实现和peek相似的功能

        in - 存所有数据
        out - 仅在pop的时候会用到
        """
        self.queue_in = deque()
        self.queue_out = deque()

    def push(self, x: int) -> None:
        """
        直接append即可
        """
        self.queue_in.append(x)


    def pop(self) -> int:
        """
        1. 首先确认不空
        2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out
        3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out
        4. 交换in和out,此时out里只有一个元素
        5. 把out中的pop出来,即是原队列的最后一个
        
        tip:这不能像栈实现队列一样,因为另一个queue也是FIFO,如果执行pop()它不能像
        stack一样从另一个pop(),所以干脆in只用来存数据,pop()的时候两个进行交换
        """
        if self.empty():
            return None

        for i in range(len(self.queue_in) - 1):
            self.queue_out.append(self.queue_in.popleft())
        
        self.queue_in, self.queue_out = self.queue_out, self.queue_in    # 交换in和out,这也是为啥in只用来存
        return self.queue_out.popleft()

    def top(self) -> int:
        """
        1. 首先确认不空
        2. 我们仅有in会存放数据,所以返回第一个即可
        """
        if self.empty():
            return None
        
        return self.queue_in[-1]


    def empty(self) -> bool:
        """
        因为只有in存了数据,只要判断in是不是有数即可
        """
        return len(self.queue_in) == 0

2、优化,使用一个队列实现

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

第二遍自己写的代码

1、用两个队列,还是沿用代码随想录说的,由于List调用pop(0)时间复杂度过高,用deque()的popleft()代替。

from collections import deque

# 我写的代码里,没有判断空的操作,默认了所有操作全部合法

class MyStack:

    def __init__(self):
        self.dq1 = deque()
        self.dq2 = deque()


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


    def pop(self) -> int:
        n = len(self.dq1)
        for i in range(n-1):
            self.dq2.append(self.dq1.popleft())
        res = self.dq1.popleft()
        self.dq2 , self.dq1 = self.dq1 , self.dq2
        return res


    def top(self) -> int:
        # 这也行啊,直接索引
        return self.dq1[-1]


    def empty(self) -> bool:
        if len(self.dq1) == 0 :
            return True
        else :
            return False

2、用一个队列,没啥好说的,会了两个队列的,就会一个队列的。

from collections import deque

# 我写的代码里,没有判断空的操作,默认了所有操作全部合法

class MyStack:

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


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


    def pop(self) -> int:
        n = len(self.dq)
        for i in range(n-1):
            self.dq.append(self.dq.popleft())
        res = self.dq.popleft()
        
        return res


    def top(self) -> int:
        # 这也行啊,直接索引
        return self.dq[-1]


    def empty(self) -> bool:
        if len(self.dq) == 0 :
            return True
        else :
            return False


20 有效的括号

括号匹配是使用栈解决的经典问题。(这个很好想,我也想到了)

由于栈结构的特殊性,非常适合做对称匹配类的题目。

只想到栈是不够的,要明确有几种不匹配的情况,这是我没有想到的。

1、第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
2、第二种情况,括号没有多余,但是 括号的类型没有匹配上。
3、第三种情况,字符串里右方向的括号多余了,所以不匹配。

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

分析完之后,代码其实就比较好写了,

但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

自己想当然的先倒序后判断是错误的

反例:“()[]{}”

class Solution:
    def isValid(self, s: str) -> bool:
        s = list(s)
        rs = s.copy()
        rs.reverse()
        n = len(s)
        
        for i in range(n):
            if s[i] == '(' and rs[i] != ')' :
                return False
                
            elif s[i] == '[' and rs[i] != ']' :
                return False

            elif s[i] == '{' and rs[i] != '}' :
                return False

            elif s[i] == ')' and rs[i] != '(' :
                return False

            elif s[i] == ']' and rs[i] != '[' :
                return False
            
            elif s[i] == '}' and rs[i] != '{' :
                return False

            else :
                pass
        return True

代码随想录的代码

确实牛,确实写不出来这些,数据结构是我的弱项。

# 方法一,仅使用栈,更省空间
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 not stack or stack[-1] != item:
                return False
            else:
                stack.pop()
        
        return True if not stack else False
# 方法二,使用字典
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        mapping = {
            '(': ')',
            '[': ']',
            '{': '}'
        }
        for item in s:
            if item in mapping.keys():
                stack.append(mapping[item])
            elif not stack or stack[-1] != item: 
                return False
            else: 
                stack.pop()
        return True if not stack else False

第二遍自己写的代码

class Solution:
    def isValid(self, s: str) -> bool:
    
        stack = []
        n = len(s)
        if n % 2 == 0 :
            for i in range(n):
                if s[i]=='(' :
                    stack.append(')')
                elif s[i]=='[' :
                    stack.append(']')
                elif s[i]=='{' :
                    stack.append('}')
                # 我这里写的和代码随想录的不一样,他用了索引,但是栈是没有索引的,所以在这里pop了之后
                # 下面的 else 直接contiue就可以了,不需要再次pop
                # 但是其实用索引也可以,就相当于调用了栈的peak()方法
                elif stack == [] or s[i] != stack.pop(): 
                    return False
                else :
                    continue
            if stack == [] :
                return True
            else :
                return False

        else :
            return False

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

唉,每道题都不会。

思路这里就不记录了,过两天复习的时候要重点过一遍“栈和队列”的所有题。

明确一点:Python中的List的append和pop方法,本质上就是栈的先进后出。

代码随想录的代码

# 方法一,使用栈
class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = list()
        for item in s:
            if res and res[-1] == item:
                res.pop()
            else:
                res.append(item)
        return "".join(res)  # 字符串拼接

这个不让使用栈,而使用双指针方法的,还是牛逼。

# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = list(s)
        slow = fast = 0
        length = len(res)

        while fast < length:
            # 如果一样直接换,不一样会把后面的填在slow的位置
            res[slow] = res[fast]
            
            # 如果发现和前一个一样,就退一格指针
            if slow > 0 and res[slow] == res[slow - 1]:
                slow -= 1
            else:
                slow += 1
            fast += 1
            
        return ''.join(res[0: slow])

第二遍自己写的代码

class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = []
        n = len(s)
        for i in range(n):
            if stack == [] :
                stack.append(s[i])
            
            elif stack[-1] == s[i] :
                stack.pop()
            else :
                stack.append(s[i])
        return ''.join(stack)

150 逆波兰表达式求值

思路:用栈操作运算,遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

递归是用栈来实现的。

栈与递归之间在某种程度上是可以转换的。

其实逆波兰表达式相当于是二叉树中的后序遍历。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。

但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后序遍历的方式把二叉树序列化了,就可以了。

在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这岂不就是一个相邻字符串消除的过程,和1047.删除字符串中的所有相邻重复项 (opens new window)中的对对碰游戏是不是就非常像了。

在这里插入图片描述

代码随想录的代码

from operator import add, sub, mul

class Solution:
    op_map = {'+': add, '-': sub, '*': mul, '/': lambda x, y: int(x / y)}
    
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for token in tokens:
            if token not in {'+', '-', '*', '/'}:
                stack.append(int(token))
            else:
                op2 = stack.pop()
                op1 = stack.pop()
                stack.append(self.op_map[token](op1, op2))  # 第一个出来的在运算符后面
        return stack.pop()

另一种可行,但因为使用eval相对较慢的方法:

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for item in tokens:
            if item not in {"+", "-", "*", "/"}:
                stack.append(item)
            else:
                first_num, second_num = stack.pop(), stack.pop()
                stack.append(
                    int(eval(f'{second_num} {item} {first_num}'))   # 第一个出来的在运算符后面
                )
        return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的

第二遍自己写的代码

向零截断,投机取巧的办法,按浮点除法做,然后int()

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        n = len(tokens)
        for i in range(n):
            if tokens[i] == '+' :
                num1 = stack.pop()
                num2 = stack.pop()
                temp = num1 + num2
                stack.append(temp)
            elif tokens[i] == '-' :
                num1 = stack.pop()
                num2 = stack.pop()
                temp = num2 - num1
                stack.append(temp)
            elif tokens[i] == '*' :
                num1 = stack.pop()
                num2 = stack.pop()
                temp = num2 * num1
                stack.append(temp)
            elif tokens[i] == '/' :
                num1 = stack.pop()
                num2 = stack.pop()
                temp = int(num2 / num1)
                stack.append(temp)
            else :
                stack.append(int(tokens[i]))
        return stack.pop()

239 滑动窗口最大值

真的绕,看不懂。

代码随想录的代码

from collections import deque


class MyQueue: #单调队列(从大到小
    def __init__(self):
        self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
    
    #每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
    #同时pop之前判断队列当前是否为空。
    def pop(self, value):
        if self.queue and value == self.queue[0]:
            self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
            
    #如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
    #这样就保持了队列里的数值是单调从大到小的了。
    def push(self, value):
        while self.queue and value > self.queue[-1]:
            self.queue.pop()
        self.queue.append(value)
        
    #查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
    def front(self):
        return self.queue[0]
    
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = MyQueue()
        result = []
        for i in range(k): #先将前k的元素放进队列
            que.push(nums[i])
        result.append(que.front()) #result 记录前k的元素的最大值
        for i in range(k, len(nums)):
            que.pop(nums[i - k]) #滑动窗口移除最前面元素
            que.push(nums[i]) #滑动窗口前加入最后面的元素
            result.append(que.front()) #记录对应的最大值
        return result

第二遍自己写的代码

from collections import deque

class DQ:
    def __init__(self):
        self.dq = deque()

    def push(self,x):
        
        if len(self.dq) == 0 :
            self.dq.append(x)
        else :
            while self.dq and x > self.dq[-1] :
                self.dq.pop()
            self.dq.append(x)

    def pop(self,x):
        if self.dq and self.dq[0]==x:
            self.dq.popleft()
    def front(self):
        return self.dq[0]


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

347 前K个高频元素

注意点:用小顶堆而不是大顶堆,堆只维护K个元素,pop走小的,剩下的才是大的。

Python中的heapq库介绍(Python中的小顶堆):上链接!

heapq介绍

代码随想录的代码

第一次接触Python中的堆,几年前学的C语言中的堆也忘干净了,所以在第二遍的时候也无法自己写出代码,就看看代码随想录的解答吧。

#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #要统计元素出现频率
        map_ = {} #nums[i]:对应出现的次数
        for i in range(len(nums)):
            map_[nums[i]] = map_.get(nums[i], 0) + 1
        
        #对频率排序
        #定义一个小顶堆,大小为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
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值