Heap & Stack & Queue Exercise 1

Posted by Ethan on April 8, 2022

Heap & Stack & Queue Exercise 1

堆(Heap)

  • 优先队列 是一种抽象的数据类型,而 是一种数据结构。所以 并不是 优先队列 是实现 优先队列 的一种方式。

  • 实现 优先队列 的方式有很多种,比如数组和链表。但是,这些实现方式只能保证插入操作和删除操作中的一种操作可以在 O(1)的时间复杂度内完成,而另外一个操作则需要在 O(N)的时间复杂度内完成

  • 能够使 优先队列 的插入操作在 O(log N)的时间复杂度内完成,删除操作在 O(log N)的时间复杂度内完成

  • 是一种特别的二叉树,满足以下条件的二叉树,可以称之为
    1. 完全二叉树;
    2. 每一个节点的值都必须 大于等于或者小于等于 其孩子节点的值。
  • 堆 具有以下的特点:

    • 可以在 O(logN)的时间复杂度内向 堆 中插入元素;
    • 可以在 O(logN)的时间复杂度内向 堆 中删除元素;
    • 可以在 O(1) 的时间复杂度内获取 堆 中的最大值或最小值
  • 堆的分类 堆 有两种类型:最大堆 和 最小堆。

    最大堆:堆中每一个节点的值 都大于等于 其孩子节点的值。所以最大堆的特性是 堆顶元素(根节点)是堆中的最大值。

    最小堆:堆中每一个节点的值 都小于等于 其孩子节点的值。所以最小堆的特性是 堆顶元素(根节点)是堆中的最小值。

1. 创建堆

  • 对于Python,使用heapq,但是只有最小堆,没有最大堆,最大堆是用取反

  • image-20220503003157991

  • 时间复杂度: O(N)

    空间复杂度: O(N)

  • image-20220503003328209

2. 往堆插入元素

image-20220503003630401

image-20220503003641357

3. 获取堆顶元素

image-20220503003724161

image-20220503003737771

4. 删除堆顶元素

image-20220503003813687

image-20220503003822798

5. 获取堆的长度

image-20220503003907623

image-20220503003939092

image-20220503003956933

image-20220503004142456

6. 堆排序

image-20220503004258687

7. TOP K问题 和 The Kth问题

方法一:

image-20220503100438086

同理,获得第k个就是重复k次,但是只取第k次

image-20220503101740173

方法二:

image-20220503101631205

image-20220503101802268

队列和栈

  • 队列是先进先出

  • 栈是先进后出

232. 用栈实现队列

[^尝试用deque实现]:

难度简单524

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

进阶:

  • 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

示例:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

思路:

  • 使用两个栈,一个作为输出栈,一个作为输入栈

    • 输入栈负责push
    • 输出栈负责pop和peek。每次需要输出的时候,输入栈将所有的内容pop到输出栈,这时输出栈里面是反向的
  • 首先是最简单的empty,只要看两个栈是不是空的判断

  • python的list的append符合push的功能,在list的末尾加上元素。所以对于push,只要在输入栈后面加上append(x)

  • 至于pop,需要考虑的是当前输出栈里面是不是空的

    • 如果是空的,将当前的输入栈内容pop进来
    • 如果非空,pop输出栈的第一个内容,使用的是python自带的pop,他会返回list末尾元素
  • 而peak可以服用pop,再把pop的元素push到stack_out

  • 初始化init时,别忘了self

    而之后提及这两个,也别忘加self

    此外,之后调用功能,比如empty,也要self! image-20211228163615659

  • empty也有问题,但是没太理解 image-20211228164822733

  • image-20211228183311419

代码:

image-20211228164837818

image-20211228164900984

225. 用队列实现栈

难度简单420

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

思路:

  • 需要使用到python的deque,也就是双向队列来完成
    • 它的操作很像list 同时相比于list实现的队列,deque实现拥有更低的时间和空间复杂度。
    • list实现在出队(pop)和插入(insert)时的空间复杂度大约为O(n),deque在出队(pop)和入队(append)时的时间复杂度是O(1)。
    • img
    • image-20211228182210511
    • deque的内部还是list,所以append和pop都可以操作,都是末尾的。对于左边的,可以使用popleft和appendleft。而这道题,我们只是模拟基础的队列,所以我们用且只会用到append和popleft
  • 首先是push操作,就是对于queue_in进行append
  • 然后是empty,可以使用和之前一样的写法,区别在于这里只用关注in
    • image-20211228185342526
    • 但是不能是==[], 而必须是长度 ==0
  • 然后是pop,对于queue_in,先popleft到只剩下一个后,和queue_out进行交换,然后queue_out再popleft。
    • 我们可以看到,queue_out是一直保持空的状态,所以,empty只需要关于queue_in即可
  • 最后是top,top要出去的是栈顶,但是queue没有pop,只有popleft。所以我们使用deque的索引,找到queue_in的最后一位
  • image-20211228183217248

代码:

image-20211228185429417

image-20211228185442972

20. 有效的括号

难度简单2853

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

示例 4:

输入:s = "([)]"
输出:false

示例 5:

输入:s = "{[]}"
输出:true

思路:

  • 首先思考什么情况下会是False
    • 左边的括号多了
    • 右边的括号多了
    • 左边和右边匹配不上
  • 使用栈来存储左侧括号,如果匹配,从栈的尾部pop出去
    • 第一个False,对应遍历完但是栈仍然非空
    • 第二个False,对应栈空了,但是没有遍历完
    • 第三个False,单纯不匹配
  • 具体实现上,对于左括号的三种形式({[, 如果匹配,在stack中存入他们的对偶形式。当发现不是这三种,也就是不是左括号时,看stack[-1],和输入中的元素是否一样。
    • 如果一样,把stack末尾pop
    • 如果不一样return False
    • 如果这个时候stack为空,但是输入还是有的,return False
  • 上面是对输入遍历,如果遍历完输入,stack非空,return False

代码:

image-20211229175316595

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

难度简单310

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

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

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

思路:

  • 和上一题很相似,使用栈来存储,再进行匹配判断,如果匹配,就pop,栈中剩下的作为输出
  • 输出结果要用“”.join()

代码:

image-20211229181715495

示例:

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

150. 逆波兰表达式求值

难度中等438

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

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

说明:

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

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = ["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

思路:

  • 使用栈解决,把数字存储到stack里面,一旦遇到符号,把栈后两位pop出来,进行运算,把结果放回栈

代码:

image-20211229204332424

image-20211229204348197

239. 滑动窗口最大值

难度困难1325

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

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

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

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

示例 3:

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

示例 4:

输入:nums = [9,11], k = 2
输出:[11]

示例 5:

输入:nums = [4,-2], k = 2
输出:[4]

思路:

  • 使用的方法是单调队列:同时弹出队首和队尾的元素是双端队列。满足这种单调性的双端队列一般称作「单调队列」。这里的单调性是队列中存储的是位置,位置对应的数字是单调递减的

  • 随着窗口滑动,会有一个新的元素进来.

    • 如果新的元素比队尾元素小,nums[i] <nums[queue[-1]], 则存入队列,
    • 如果比队尾元素大或一样,则将队尾元素pop出去,直到发现有一个队列中元素比他大,或者他来到了队列最前端。
    • 对于前k个可以这么做来找到queue[0],存储到ans中
  • 对于k到n,此时每滑动一次都要将queue[0]对应的值放入ans

    • 相比起前k个。需要额外考虑的是:万一一直是最先进来的元素最大,比如[5,4,3,2,1],那么首部将不会被赶走。所以需要不断看当前queue中第一位所存储的位置是否满足queue[0]>i-k,如果不满足需要popleft把头部去掉
  • 注意,生成第一个queue的元素,要经历好几次pop。所以不能分开来,要在一个while里面: image-20211229234910996

    image-20211229235256929

代码:

image-20211229235235768

347. 前 K 个高频元素

难度中等961

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

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

示例 2:

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

思路:

  • 三件事情,计算并存储频率,频率比较,拿出前k个高频的

  • 对于计算并且存储频率,使用Python字典实现hashmap,使用hashmap存储每个数字的频率。

    • 使用Python 字典get() 函数返回指定键的值

    • dict.get(key, default=None)
      
    • key – 字典中要查找的键。

    • default – 如果指定的键不存在时,返回该默认值。

    • 根据每个key,不断查询该key的出现频率,并进行更新

    • image-20211230074024782
  • 对于频率比较,使用优先队列。

    • 这里使用heapq模块来进行堆操作。堆(heap)是一种优先队列。优先队列让你能够以任意顺序添加对象,并随时找出(并删除)最小的元素。相比于列表方法min,这样做的效率要高得多。
    • image-20211208182112339
    • 创建heap=[]。然后将刚才得到的键值对压入heap中,注意压入负值,即负的频率。这样到时候pop出来的就是从大到小的顺序。
    • 此外,需要注意遍历heapmap的key, value的时候要加.items()
    • image-20211230074226562
  • 最后是前k个,进行heappop(heap)[1]。heappop得到的是最前面的,是负的最厉害的频率,也就是最大频率。[1]是他对于的键,因为heap里面存的是-value和key

代码:

image-20211208184305119

155. 最小栈

难度简单1124

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • getMin() —— 检索栈中的最小元素。

示例:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2
Input: heights = [2,4]
Output: 4