踏入前行的阶梯:JavaScript栈与队列系列专题之239题和347题巧妙解题思路
2023-10-25 03:56:37
算法世界的大门:栈与队列
对于初入算法编程领域的探索者来说,栈和队列是两个不可忽视的数据结构。栈遵循“后进先出”(LIFO)的原则,就像一叠盘子,后放的盘子先取出来。队列则遵循“先进先出”(FIFO)的原则,就像排队等候,先排队的人先被服务。
在计算机程序中,栈和队列可以用于解决一系列常见的问题。例如,栈可用于管理函数调用,确保先调用的函数先返回;队列可用于处理任务调度,保证先提交的任务先执行。理解栈和队列的工作方式,是算法编程入门的基础。
算法实战:滑动窗口最大值和前K个高频元素
-
239. 滑动窗口最大值
滑动窗口最大值问题如下:给定一个数组nums和一个滑动窗口的尺寸k,请找出所有滑动窗口中的最大值。
面对这个问题,Carl老师提出了两种解法。
方法一:暴力求解
这种方法简单直接,但效率较低。具体步骤如下:
- 从数组nums的开头开始,逐个元素地遍历整个数组。
- 对于每个元素,计算以该元素为结尾的滑动窗口中的最大值。
- 将计算出的最大值存储在结果数组中。
方法二:使用双端队列
双端队列(也称作deque)是一种特殊的队列,它允许在队头和队尾两端进行插入和删除操作。利用双端队列的特性,我们可以更高效地解决滑动窗口最大值问题。
具体步骤如下:
- 将滑动窗口中的元素依次加入双端队列。
- 当队列中的元素个数超过k时,将队首元素(即最先加入队列的元素)弹出。
- 此时,队首元素就是滑动窗口中的最大值。
- 重复步骤2和步骤3,直到遍历完整个数组。
示例代码:
// 方法一:暴力求解 const maxSlidingWindow1 = (nums, k) => { const result = []; for (let i = 0; i <= nums.length - k; i++) { let max = nums[i]; for (let j = i + 1; j < i + k; j++) { max = Math.max(max, nums[j]); } result.push(max); } return result; }; // 方法二:使用双端队列 const maxSlidingWindow2 = (nums, k) => { const result = []; const queue = []; for (let i = 0; i < nums.length; i++) { while (queue.length > 0 && nums[queue[queue.length - 1]] < nums[i]) { queue.pop(); } queue.push(i); if (i - queue[0] >= k) { queue.shift(); } if (i >= k - 1) { result.push(nums[queue[0]]); } } return result; };
-
347. 前K个高频元素
前K个高频元素问题如下:给定一个数组nums和一个整数k,请找出数组中出现频率前k高的元素。
Carl老师同样提供了两种解决方法。
方法一:哈希表
使用哈希表来统计每个元素的出现频率。具体步骤如下:
- 创建一个哈希表,并将数组nums中的每个元素作为哈希表的键,元素出现的次数作为哈希表的值。
- 遍历哈希表,找出出现频率前k高的元素。
- 将这些元素按照出现频率从高到低排序。
- 返回排序后的前k个元素。
方法二:桶排序
桶排序是一种非比较排序算法,它将数组中的元素分配到多个桶中,然后对每个桶中的元素进行排序。利用桶排序,我们可以更高效地解决前K个高频元素问题。
具体步骤如下:
- 计算数组nums中元素的最大值和最小值。
- 根据最大值和最小值,创建若干个桶。
- 将数组nums中的元素分配到相应的桶中。
- 对每个桶中的元素进行排序。
- 将各个桶中的元素按照出现频率从高到低连接起来。
- 返回排序后的前k个元素。
示例代码:
// 方法一:哈希表 const topKFrequent1 = (nums, k) => { const map = new Map(); for (const num of nums) { map.set(num, (map.get(num) || 0) + 1); } const sortedMap = new Map([...map.entries()].sort((a, b) => b[1] - a[1])); const result = []; for (const [num, count] of sortedMap) { result.push(num); if (result.length === k) { break; } } return result; }; // 方法二:桶排序 const topKFrequent2 = (nums, k) => { const maxValue = Math.max(...nums); const minValue = Math.min(...nums); const bucketSize = Math.floor((maxValue - minValue) / k) + 1; const buckets = []; for (let i = 0; i <= k; i++) { buckets.push([]); } for (const num of nums) { const bucketIndex = Math.floor((num - minValue) / bucketSize); buckets[bucketIndex].push(num); } const result = []; for (const bucket of buckets) { bucket.sort((a, b) => b - a); result.push(...bucket); if (result.length >= k) { break; } } return result.slice(0, k); };
总结与展望
通过对以上两道经典算法题的深入解析,我们不仅学习了具体的问题求解方法,也对栈和队列等基础数据结构有了更深刻的理解。这些知识和经验将为我们日后的算法编程实践奠定坚实的基础。
值得一提的是,本文仅探讨了最基本的数据结构和算法的应用,算法世界还有着广阔的领域等待我们去探索。保持对算法的热情和好奇心,不断学习和实践,才能在算法思维的道路上不断精进,解锁更多难题的解决方案。