队列集训:科普队列概念与leetcode相关题目精讲
Author:zhoulujun Date:
队列(Queue)
队列(Queue):一种线性表数据结构,是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表。
简单来说,队列是一种 「先进先出(First In First Out)」 的线性表,简称为 「FIFO 结构」。
更详细的,推荐阅读:https://algo.itcharge.cn/04.Queue/01.Queue-Basic/01.Queue-Basic/
优先队列(Priority Queue)
优先队列(Priority Queue):一种特殊的队列。在优先队列中,元素被赋予优先级,当访问队列元素时,具有最高优先级的元素最先删除。
优先队列与普通队列最大的不同点在于 出队顺序。
普通队列的出队顺序跟入队顺序相关,符合「先进先出(First in, First out)」的规则。
优先队列的出队顺序跟入队顺序无关,优先队列是按照元素的优先级来决定出队顺序的。优先级高的元素优先出队,优先级低的元素后出队。优先队列符合 「最高级先出(First in, Largest out)」 的规则。
优先队列的适用场景
优先队列的应用场景非常多,比如:
数据压缩:赫夫曼编码算法;
最短路径算法:Dijkstra 算法;
最小生成树算法:Prim 算法;
任务调度器:根据优先级执行系统任务;
事件驱动仿真:顾客排队算法;
排序问题:查找第 k 个最小元素。
更详细的推荐阅读:https://algo.itcharge.cn/04.Queue/02.Priority-Queue/01.Priority-Queue/
leetcode题目:
LeetCode上有很多题目涉及到队列(Queue)的使用,包括普通队列、循环队列、双端队列(Deque)、优先队列等。下面是一些与队列相关的典
队列的顺序存储与链式存储
队列实现
232. 用栈实现队列 (Implement Queue using Stacks):实现一个队列,只允许使用栈来完成。
622. 设计循环队列 (Design Circular Queue):设计一个循环队列的数据结构,包括队列的插入、删除、检查是否为空和检查是否已满等
641. 设计循环双端队列 (Design Circular Deque):设计一个循环双端队列,允许在两端进行插入和删除操作
1670. 设计前中后队列 (Design Front Middle Back Queue):设计一个队列,允许在队列的前端、中间和后端进行插入和删除操作
队列题
933. 最近的请求次数 (Number of Recent Calls):记录最近的N个请求,并返回在过去3000毫秒内被调用的次数。
859. 亲密字符串 (Buddy Strings):检查两个字符串是否可以通过交换一次字符变成相同的字符串。
860. 柠檬水找零 (Lemonade Change):模拟顾客购买柠檬水的过程,处理找零问题,确保有足够的零钱找给顾客。
969. 煎饼排序 (Pancake Sorting):通过一系列翻转操作将一个数组按升序排序。
621. 任务调度器 (Task Scheduler):给定一个任务列表和冷却时间,确定完成所有任务所需的最短时间。
338. 比特位计数 (Counting Bits):计算从0到给定整数n之间的每个数字的二进制表示中1的个数。
优先队列相关题目
215. 数组中的第K个最大元素 (Kth Largest Element in an Array):找到未排序数组中的第k个最大元素。
23. 合并K个排序链表 (Merge k Sorted Lists):合并多个已排序的链表。
973.最接近原点的 K 个点
239. 滑动窗口最大值 (Sliding Window Maximum):在给定数组的每个滑动窗口中找到最大值。
前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
示例 2: 输入: nums = [1], k = 1 输出: [1]
最简单粗暴的思路就是 使用排序算法对元素按照频率由高到低进行排序,然后再取前 k 个元素。
排序中,可以使用桶排序(推荐阅读《再谈桶排排序算法—从计数排序到桶排序》)
桶排序法
首先依旧使用哈希表统计频率,统计完成后,创建一个数组,将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标即可。
//基于桶排序求解「前 K 个高频元素」 class Solution { public List<Integer> topKFrequent(int[] nums, int k) { List<Integer> res = new ArrayList(); // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值 HashMap<Integer,Integer> map = new HashMap(); for(int num : nums){ if (map.containsKey(num)) { map.put(num, map.get(num) + 1); } else { map.put(num, 1); } } //桶排序 //将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标 List<Integer>[] list = new List[nums.length+1]; for(int key : map.keySet()){ // 获取出现的次数作为下标 int i = map.get(key); if(list[i] == null){ list[i] = new ArrayList(); } list[i].add(key); } // 倒序遍历数组获取出现顺序从大到小的排列 for(int i = list.length - 1;i >= 0 && res.size() < k;i--){ if(list[i] == null) continue; res.addAll(list[i]); } return res; } }
哈希表 + 优先队列
使用哈希表统计元素频率: 首先遍历数组,使用哈希表统计每个元素出现的频率。
使用优先队列获取前 K 高频元素: 将哈希表中的元素和对应的频率存入优先队列中。优先队列可以用 JavaScript 中的最小堆来实现。
取出前 K 高频元素: 从优先队列中取出前 K 个元素即可。
var topKFrequent = function (nums, k) { // Step 1: Count frequency using a hash map const freqMap = new Map(); nums.forEach(num => { freqMap.set(num, (freqMap.get(num) || 0) + 1); }); // Step 2: Create PriorityQueue const pq = new PriorityQueue((a, b) => a.freq - b.freq); for (const [num, freq] of hashTable.entries()) { if (pq.size < k) { pq.push({ num, freq }); } else if (freq > pq.top().freq) { pq.pop(); pq.push({ num, freq }); } } // Step 3: Collect the result const result = []; while (pq.size > 0) { result.push(pq.pop().num); } return result; };
有先队列JavaScript实现(一般使用第三方库)
class PriorityQueue { constructor() { this.data = []; } enqueue(element) { this.data.push(element); this.bubbleUp(this.data.length - 1); } dequeue() { if (this.data.length === 0) return null; const top = this.data[0]; this.data[0] = this.data[this.data.length - 1]; this.data.pop(); this.sinkDown(0); return top; } size() { return this.data.length; } bubbleUp(index) { while (index > 0 && this.compare(this.data[Math.floor((index - 1) / 2)], this.data[index])) { const parentIndex = Math.floor((index - 1) / 2); [this.data[index], this.data[parentIndex]] = [this.data[parentIndex], this.data[index]]; index = parentIndex; } } sinkDown(index) { let childIndex = (index * 2) + 1; while (childIndex < this.data.length) { const rightChildIndex = childIndex + 1; if ( rightChildIndex < this.data.length && this.compare(this.data[childIndex], this.data[rightChildIndex]) ) { childIndex = rightChildIndex; } if (this.compare(this.data[index], this.data[childIndex])) { return; } [this.data[index], this.data[childIndex]] = [this.data[childIndex], this.data[index]]; index = childIndex; } } compare(a, b) { return a.count < b.count; // 降序排列 } }
根据字符出现频率排序
给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
示例 1: 输入: s = "tree" 输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2: 输入: s = "cccaaa" 输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。 注意"cacaca"是不正确的,因为相同的字母必须放在一起
优先队列
使用哈希表 s_dict 统计字符频率。
然后遍历哈希表 s_dict,将字符以及字符频数存入优先队列中。
将优先队列中频数最高的元素依次加入答案数组中。
最后拼接答案数组为字符串,将其返回。
function sortCharactersByFrequency(str) { const hashTable = new Map(); for (const char of str) { hashTable.set(char, hashTable.get(char) || 0) + 1; } const priorityQueue = new PriorityQueue((a, b) => b.count - a.count); for (const [char, count] of hashTable.entries()) { priorityQueue.enqueue({ char, count }); } let result = ""; while (priorityQueue.size()) { const { char } = priorityQueue.dequeue(); result += char; } return result; }
滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1: 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7]
这个和直接滑动窗口是不一样的(回忆一下《双指针:对撞/快慢/分离/滑动窗口的案例(数组/回文/求和等) 》),这个是是如何求一个区间里的最大值呢?
此时我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。
每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。
function maxSlidingWindow(nums, k) { if (k > nums.length) return []; const pq = new PriorityQueue((a, b) => a - b); const result = []; for (let i = 0; i < nums.length; i++) { pq.enqueue(nums[i]); if (pq.size() > k) { pq.dequeue(); } if (i >= k - 1) { result.push(pq.top()); } } return result; }
使用优先队列求上面的解,代码非常简洁。
参考文章:
https://algo.itcharge.cn/04.Queue/02.Priority-Queue/01.Priority-Queue/
https://programmercarl.com/0239.滑动窗口最大值.html
转载本站文章《队列集训:科普队列概念与leetcode相关题目精讲》,
请注明出处:https://www.zhoulujun.cn/html/theory/algorithm/leetcode/9102.html