在js这么语言中,并没有直接定义栈与队列,它只有数组的定义以及数组的一些操作函数。那么我们怎样在js中定义出栈与队列呢?今天我们来探讨这个问题。
首先,我们要明确数组有哪些增删元素的函数。如下所示:
// 添加元素
numbers.push(60); // 在数组末尾添加 60,此时数组为[10, 20, 30, 40, 50, 60]
numbers.unshift(5); // 在数组开头添加 5,此时数组为[5, 10, 20, 30, 40, 50, 60]
//删除元素
numbers.pop(); // 删除最后一个元素,此时的数组为[5, 10, 20, 30, 40, 50]
numbers.shift(); // 删除第一个元素,此时的数组为[10, 20, 30, 40, 50]
// 使用 splice() 插入和删除
numbers.splice(2, 0, 25); // 参数'2'表示的是在下标为2的位置插入第三个参数'25','0'是从下标'2'开始要删除的元素个数,此时数组为[10, 20, 25, 30, 40, 50]
1.栈
定义:栈是一种遵循先进后出原则的数据结构,可以想象成一个下端闭口上端开口的容器,放入与取出都只能从上端开口进行,这意味着最后被压入栈中的元素是第一个被弹出的元素。
而要想在js中实现这样的定义,我们要让数组严格的遵循先进后出的原则,那这样我们就可以把这个弱化的数组称为栈。那这样的话,我们在增删数据时,只可以使用 push() + pop() 或者 unshift() + shift() 进行操作
const stack = []
//入栈
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
stack.push(5)
// 出栈
while(stack.length){
for(i = 1;i <= stack.length; i++){
console.log(`第${i}个出栈的是${stack.pop()}`);
}
}
结果为:
遵循了栈先进后出的原则
2.队列
定义:队列是一种遵循先进先出原则的数据结构,可以想象成一个左进右出的管道,这意味着首先被插入队列中的元素是第一个被删除的元素。
而同样,要想在js中实现这样的定义,只可以使用 push() + shift() 和 unshift() + pop() 进行操作
const queue = []
//入队
queue.push(1)
queue.push(2)
queue.push(3)
queue.push(4)
queue.push(5)
// 出队
while(queue.length){
for(i = 1;i <= queue.length; i++){
console.log(`第${i}个出队列的是${queue.shift()}`);
}
}
结果为:
遵循了队列先进先出的原则
运用:
了解完这些后,我们可以通过一些算法题来巩固一下
1.LeetCode 232 —— 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 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(双端队列)来模拟一个栈,只要是标准的栈操作即可。
解题思路:
要用两个栈实现队列大体框架其实很容易就能想到,如上图所示。先往一个栈中push()进数据,再pop()出数据push()进另外一个栈再pop()出就能输出与输入一样顺序的数据,从而就可以满足队列先进先出的原则。
做到这里,可能有些小伙伴就想着哎 原来这个题目如此的简单,但是运行代码又会发现,测试不通过。而这又是为什么呢?原来,是还漏掉了其他一些情况,如果stack2里的数据还没完全pop()出去,而此时又往stack1中push()进了新的数据,然后又被pop()到了stack2中,那此时先输出的就是这个刚被push()进stack2中的那个新数据了,这样就不满足先入先出的原则了
如图所示:
那要如何解决这个问题呢?在往 stack2pop()数据前需要判断一下stack1是否还有数据,如果还有数据,那就需要先将stack1中的数据先存入stack2中,当数据全部存入stack2中时才可以 pop() 出数据,同样int peek() 返回队列开头的元素也需要进行这个判断。
完整代码如下:
var MyQueue = function() {
this.stack1 = []
this.stack2 = []
};
MyQueue.prototype.push = function(x) {
this.stack1.push(x)
};
MyQueue.prototype.pop = function() {
if(this.stack2.length <= 0){
while(this.stack1.length){
this.stack2.push(this.stack1.pop())
}
}
return this.stack2.pop()
};
MyQueue.prototype.peek = function() {
if(this.stack2.length <= 0){
while(this.stack1.length){
this.stack2.push(this.stack1.pop())
}
}
const len = this.stack2.length
return this.stack2[len - 1]
};
MyQueue.prototype.empty = function() {
return !(this.stack1.length + this.stack2.length) //!是将int转换为bool类型
};
2.LeetCode 239 ——滑动窗口最大值
给你一个整数数组 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]
提示:
1 <= nums.length <= 105-104 <= nums[i] <= 1041 <= k <= nums.length
当看到这个题目的时候,第一想法肯定是用双指针去求解,这样当然可以求解,但是有一个很大的弊端,就是每次在两个指针之间都要求这三个数的最大值,那么如果这个数组很大呢?那就需要进行很多次的比较了,但是有时候一直移动它的最大值其实一直都没有变,例如例子中前两次的框选最大值都是三,那这样一直比较只会增加开销。
这时,我们就可以引入一个新的概念——“双端队列”
双端对列,顾名思义,这个队列的两端都可以进出
那我们要如何减少每次双指针之间的数都要比较这一问题呢,引入一个双端队列,让最大值一直处于队列的对头,有比它大的值出现时才会将其替代,就这样一直便遍历到后面,最大值一直在对头,并且这个队列里的元素呈现的是一个单调递减的样子。此时,还要注意一个问题,那就是这个k值小大,那在这个遍历过程中,可能这个最大值已经不处于这个窗口之间了,那么也要将其移除。
这两个问题对应的也就是下面这两块代码,需要着重理解
for(let i = 0; i < len; i++){
while(deque.length && nums[deque[deque.length - 1]] < nums[i]){
deque.pop(); // 移除小于当前元素的索引
}
deque.push(i); // 将当前索引压入队列
- 这部分循环遍历数组
nums,i是当前元素的索引。 - 内部的
while循环确保deque中的元素索引对应的值是递减的:每当遇到一个比队列尾部对应元素更大的元素时,将尾部元素移除,保持deque中索引对应的值从大到小。 - 然后将当前元素的索引
i添加到deque中。
// 当队列头部存放的值和 i 形成的区间大于窗口宽度时
while(deque.length && deque[0] <= i - k){
deque.shift(); // 移除过期的索引
}
- 这个
while循环检查deque的头部元素(索引)是否已经超出了当前窗口的范围i - k。如果deque[0]这个索引已经在窗口范围之外,就将其移除,保证deque中的索引始终是当前窗口内的。
完整代码展示:
var maxSlidingWindow = function(nums, k) {
const len = nums.length
const res = []
const deque = [] // 用于存放索引的双端队列
for(let i = 0;i < len; i++){
while(deque.length && nums[deque[deque.length - 1]] < nums[i]){
deque.pop() // 移除小于当前元素的索引
}
deque.push(i) // 将当前索引压入队列
//当队列头部存放的值和 i 形成的区间大于窗口宽度时
while(deque.length && deque[0] <= i - k){
deque.shift() // 移除过期的索引
}
// 该取最大值的时候
if(i >= k - 1){
res.push(nums[deque[0]])
}
}
return res
};
看到这里,相信大家对JS中栈与队列的使用更加的熟悉了,如果对大家有所帮助的话,那么请大家: