0
点赞
收藏
分享

微信扫一扫

【IDE 小程序】小程序控制台 不打印 console.log问题

兽怪海北 2023-07-13 阅读 44

目录

栈与队列

理论基础

232.用栈实现队列

225. 用队列实现栈

20. 有效的括号

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

150. 逆波兰表达式求值

239.滑动窗口最大值

347.前 K 个高频元素


栈与队列

理论基础

队列是先进先出,栈是先进后出。

232.用栈实现队列

使用栈实现队列的下列操作:

push(x) -- 将一个元素放入队列的尾部。

pop() -- 从队列首部移除元素。

peek() -- 返回队列首部的元素。

empty() -- 返回队列是否为空。

class MyQueue:
 
    def __init__(self):
        """
        in主要负责push,out主要负责pop
        """
        self.stack_in = []
        self.stack_out = []
# python的list像栈,先入后出。需要实现队列,先入先出。
# ✔
 
    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_in:
        #     for i in range(len(self.stack_in)):
        #         self.stack_out.append(self.stack_in.pop())
        # return self.stack_out.pop()
# 以上会出错,例如:
# ["MyQueue","push","push","push","push","pop","push","pop","pop","pop","pop"]
# [[],[1],[2],[3],[4],[],[5],[],[],[],[]]
# 输出
# [null,null,null,null,null,1,null,5,2,3,4]
# 预期结果
# [null,null,null,null,null,1,null,2,3,4,5]
# push[5]时,基于先入先出的原则,[5]应该最后被pop出来。但按照上面的错误流程,在out已经有值的情况下,[5]还是会被从in转移到out然后被先pop出来。
# 正确应该是先把out里的全pop完然后再把[5]从in转移到out然后pop。
        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()
        
# 为什么上面有self.stack_out.pop(),下面有self.pop(),这两个有什么区别?
# 区别在于,self.stack_out.pop()调用的不是我们在这里定义的pop函数。它调用的是stack_out作为list类的库函数,
# 是list类固有的。每个list都有,随便新定义一个list,它也可以用.pop方法,返回的是list最末位的元素。
# 例如 定义list0 = [1,2,3,4],要求return list0.pop(), list0,返回值为[4,[1,2,3]]
# 下面的self.pop()是调用我们在这里定义的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)

225. 用队列实现栈

使用队列实现栈的下列操作:

push(x) -- 元素 x 入栈

pop() -- 移除栈顶元素

top() -- 获取栈顶元素

empty() -- 返回栈是否为空

注意:

你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。

你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

class MyStack:
 
    def __init__(self):
        self.que = deque()
 
    def push(self, x: int) -> None:
        self.que.append(x)
 
    def pop(self):
        if self.empty():
            return None
        # for i in range(len(self.que) - 1):
        #     self.que.append(self.que.pop()) # 错误的。deque()的pop()是先入后出的pop
        #     # 例如[1,2,3],pop()返回的是3而不是想象中的1.
        #     # 所以上式直接是,pop3然后push3然后pop2然后push2,最后deque还是没变。。
        #     # 应该用popleft()实现队列的先入先出,[1,2,3]返回1.
        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:
        if self.que != deque():
            return False
        else:
            return True
        # 也可以用下面这个来判断,更简单直接    
        # return not self.que

20. 有效的括号

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。

左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例 1:

输入: "()"

输出: true

示例 2:

输入: "()[]{}"

输出: true

示例 3:

输入: "(]"

输出: false

示例 4:

输入: "([)]"

输出: false

示例 5:

输入: "{[]}"

输出: true

括号匹配是使用栈解决的经典问题。

先来分析一下 这里有三种不匹配的情况,

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 
  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 
  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 
class Solution:
    def isValid(self, s: str) -> bool:
        st = []
        for i in range(len(s)):
            if s[i] == '(':
                st.append(')')
            elif s[i] == '{':
                st.append('}')
            elif s[i] == '[':
                st.append(']')
            elif st == [] or s[i] != st[-1]:
                return False
            else:
                st.pop()
        return False if st else True

算法题做题前先想想能不能第一步先剪枝。降低计算量。

修改后在开头增加剪枝的两行,判断字符串长度是否为单数,若为单数则必不可能全成对。

if len(s) % 2 != 0:

return False

可改进:

for i in range(len(s)):换成 for item in s:

elif st == []换成 elif not st

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

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:"abbaca"

输出:"ca"

解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

提示:

1 <= S.length <= 20000

S 仅由小写英文字母组成。

本题是匹配相邻元素,最后都是做消除的操作。

本题也是用栈来解决的经典题目。

从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。

class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = []
        for item in s:
            if not res or item != res[-1]:#为空或值不同则增
                res.append(item)
            else:#值同则消
                res.pop()
        return ''.join(res)
150. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的运算符包括 + ,  - ,  * ,  / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

向零取整(向零截断) 取距离0最近的精确整数值。

示例 1:

输入: ["2", "1", "+", "3", " * "]

输出: 9

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

示例 2:

输入: ["4", "13", "5", "/", "+"]

输出: 6

解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]

输出: 22

解释:该算式转化为常见的中缀算术表达式为:

((10 * (6 / ((9 + 3) * -11))) + 17) + 5       

= ((10 * (6 / (12 * -11))) + 17) + 5       

= ((10 * (6 / -132)) + 17) + 5     

= ((10 * 0) + 17) + 5     

= (0 + 17) + 5    

= 17 + 5    

= 22    

逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

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

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

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        res = []
        for item in tokens:
            if item == '+' or item == '-' or item == '*' or item == '/':
                nums1 = res.pop()
                nums2 = res.pop()
                if item == '+':
                    res.append(nums1 + nums2)
                if item == '-':
                    res.append(nums2 - nums1)
                    # 注意不是nums1 - nums2 因为pop的顺序
                if item == '*':
                    res.append(nums1 * nums2)
                if item == '/':
                    # res.append(nums2 // nums1)
                    # 上式不对。例如6/(-132)结果本来应该是0但是这个除法(//)(也称地板除)会得到-1的结果。正确应该是:
                    res.append(int(nums2 / nums1))
            else:
                res.append(int(item))
        return res.pop()

eval()是python中功能非常强大的一个函数

将字符串当成有效的表达式来求值,并返回计算结果

所谓表达式就是:eval这个函数会把里面的字符串参数的引号去掉,把中间的内容当成Python的代码,eval函数会执行这段代码并且返回执行结果

stack.append( int(eval(f'{second_num} {item} {first_num}'))) 

239.滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

进阶:

你能在线性时间复杂度内解决此题吗?

提示:

1 <= nums.length <= 10^5

-10^4 <= nums[i] <= 10^4

1 <= k <= nums.length

这是使用单调队列的经典题目。

难点是如何求一个区间里的最大值呢? (这好像是废话),暴力一下不就得了。

暴力方法,遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法。

分析一下,队列里的元素一定是要排序的,而且要最大值放在出队口,要不然怎么知道最大值呢。

但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素。

那么问题来了,已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢。

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。

对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。

进2:2

进3:3

进5:5

进1:5 1

进4:5 4

后来的大的吃小的

后来的小的保留下

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

  1. pop(value):如果窗口移动的时候左侧pop的元素的value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作。此时单调队列继续往后增加值而不弹出值
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,并继续移动滑窗,若push进滑窗的是小值则保留在单调队列里。
# deque 是Python标准库 collections 中的一个类,实现了两端都可以操作的队列,相当于双端队列
# append(item),添加一个数据到队列的尾部。与列表的append()方法功能相似。
# appendleft(item),添加一个数据到队列的头部。与append()的添加方向相反。
# pop(),将队列尾部的数据弹出,并作为返回值。
# popleft(),将队列头部的数据弹出,并作为返回值。
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        
        # 视频里讲的是左边pop出,右边push进(但push进的时候涉及右边出和进 视频里用push_back和pop_back),左边大右边小
        # 这里尝试右边pop出,左边push进(但涉及appendleft和popleft),左边小右边大
        def pop(que, val): #(右出)
            # 如果没有这个if条件会怎样。因为但凡是需要pop的好像都是pop最大值
            # 回答:必须要有if。否则,比如-1 3 1 -3,滑动到3 1 -3的时候要pop出-1,但此时-1已经被3卷走了,所以滑动窗口里本来也没-1,因此不需要进行pop操作。
            # if not que and que[-1] == val: 判断条件不是not que而是que,表示que不是空而是有值的时候
            if que and que[-1] == val: # 此时pop的就是当前窗口最大值
                que.pop()
            return que
        
        def push(que, val): # (左出左进)
            while que and que[0] < val:
                que.popleft()
            que.appendleft(val)
            return que
        que = deque()
        res = []
        for i in range(len(nums)):
            if i < k:
                que = push(que, nums[i])
            else:
                res.append(que[-1])
                # 注意append是用小括号()而不是中括号[]
                que = push(que, nums[i])
                que = pop(que, nums[- k])
                # 上两行交换会否有影响?不能交换,要先push再pop,否则可能push进的是当前窗口的最大值但由于pop先于push,导致pop的还是上一个窗口的最大值。
        res.append(que[-1])
        return res
          
 
        
# 下面是卡哥的
# 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
347.前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2

输出: [1,2]

示例 2:

输入: nums = [1], k = 1

输出: [1]

提示:

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。

你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小。

题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。

你可以按任意顺序返回答案。

这道题目主要涉及到如下三块内容:

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前K个高频元素

首先统计元素出现的频率,这一类的问题可以使用map来进行统计。

然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列

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

所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素)

我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

# 以下为自己学完之后写的版本
import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        map_ = {}
        for item in nums:
            map_[item] = map_.get(item, 0) + 1
            # map_.get(item, 0)表示若有item则返回item否则返回0
        pri_que = []
        # for (key, freq) in map_: ✖
        for (key, freq) in map_.items():
            if len(pri_que) < k:
                heapq.heappush(pri_que, (freq, key))
            else:
                heapq.heappush(pri_que, (freq, key))
                heapq.heappop(pri_que)
                # 必须先push再pop,否则不对。
        return [i[1] for i in pri_que]
 
# # python内置模块 --- heapq:实现了一个小根堆。
# # 模块内常见的操作函数:
# # 1. heapify(x) :    将列表x转换为堆(小根堆)。
# # 2. heappush(heap,item): 将item压入堆中。(heap使存放堆的数组)
# # 3. heappop(heap): 从堆中弹出最小的项,并将其值返回。
# # heapq堆数据结构最重要的特征是heap[0]永远是最小的元素
# # ————————————————
# # heapq.heapify(list)
# # 将列表转换为最小堆 ,转换完后仍然是列表,所以heapify只是对列表中的元素进行了堆属性的排序
 
# # import heapq
# # res=[7,1,5,4,2,3]
# # print("列表",res,type(res))     #列表 [7, 1, 5, 4, 2, 3] 
# # heapq.heapify(res)
# # print("最小堆",res,type(res))   #最小堆 [1, 2, 3, 4, 7, 5] 
# # ————————————————
# # heapq.heappush(heap, item)
# # 将元素item压入heap堆(列表)中
 
# # import heapq
# # res=[7,1,5,6,2,3]
# # heapq.heapify(res)
# # heapq.heappush(res,8)
# # print("最小堆",res,type(res))   #最小堆 [1, 2, 3, 6, 7, 5, 8] 
# # ————————————————
# # heapq.heappop(heap) 
# # 删除并返回最小值,因为堆的特征是heap[0]永远是最小的元素,所以一般都是删除第一个元素。故,需要先通过heapify(list) 将list转变为heap。
 
# # import heapq
# # res=[7,1,5,6,2,3]
# # print("列表",res,type(res))      #列表 [7, 1, 5, 6, 2, 3] 
# # heapq.heapify(res)
# # min_d=heapq.heappop(res)
# # print(min_d)                     #1
# # print("最小堆",res,type(res))    #最小堆 [2, 5, 3, 6, 7] 

举报

相关推荐

0 条评论