title: 栈和队列结合leetcode
categories: 数据结构与算法
tag:
- leetcode
- 栈和队列
date: 2021-09-16 20:26:34
栈与队列
栈和队列的描述
-
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈后进先出
-
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
-
队列先进先出
1. 用栈实现队列
- leetCode 232 用栈实现队列
思路
队列是先进先出,栈是先进后出,我们要使用两个栈来模拟队列的特性
当出队栈存在内容时,出队栈的栈顶,即为第一个出队的元素。
若出队栈无元素,我们的需求又是出队的话,我们就需要将入队栈的内容反序导入出队栈,然后弹出栈顶即可。
var MyQueue = function() {
this.is = [];
this.os = [];
};
MyQueue.prototype.push = function(x) {
this.is.push(x);
};
MyQueue.prototype.pop = function() {
if(!this.os.length){
while(this.is.length){
this.os.push(this.is.pop());
}
}
return this.os.pop();
};
MyQueue.prototype.peek = function() {
if(!this.os.length){
while(this.is.length){
this.os.push(this.is.pop());
}
}
return this.os[this.os.length - 1];
};
MyQueue.prototype.empty = function() {
return !this.is.length && !this.os.length;
};
2. 用队列实现栈
- leetCode225 用队列实现栈
// 使用一个队列实现
/**
* Initialize your data structure here.
*/
var MyStack = function() {
this.queue = []
}
/**
* Push element x onto stack.
* @param {number} x
* @return {void}
*/
MyStack.prototype.push = function(x) {
this.queue.push(x)
}
/**
* Removes the element on top of the stack and returns that element.
* @return {number}
*/
MyStack.prototype.pop = function() {
let size = this.queue.length
while (size-- > 1) {
this.queue.push(this.queue.shift())
}
return this.queue.shift()
}
/**
* Get the top element.
* @return {number}
*/
MyStack.prototype.top = function() {
const x = this.pop()
this.queue.push(x)
return x
}
/**
* Returns whether the stack is empty.
* @return {boolean}
*/
MyStack.prototype.empty = function() {
return !this.queue.length
}
3. 有效的括号
- leetCode 20 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
思路
- 遍历字符串,然后把他们放进一个栈中,遇到左括号类型就入栈,遇到有括号类型就将它与栈顶元素作匹配,若匹配成功,继续遍历。
- 遍历结束后判断栈的长度是否为0,若为0,就返回true.
const isValid = function(s) {
let stack = []
for (let i = 0; i < s.length; i++) {
let c = s[i]
switch (c) {
case '(':
case '[':
case '{':
stack.push(c)
break
case '}':
if (stack.pop() !== '{') return false
break
case ']':
if (stack.pop() !== '[') return false
break
case ')':
if (stack.pop() !== '(') return false
break
}
}
return stack.length === 0
}
4. 删除字符串中所有相邻的重复项
- leetCode 1047 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。
思路
与上题思路类似,每次进栈前,与栈顶元素作对比,如果与栈顶元素相同,就出栈,不同就进栈。
/**
* @param {string} s
* @return {string}
*/
var removeDuplicates = function(S) {
let stack = []
for (let item of S) {
if (stack[stack.length - 1] === item) {
stack.pop()
} else {
stack.push(item)
}
}
return stack.join('')
}
console.log(removeDuplicates('abbaca'))
5. 逆波兰表达式求值
- leetCode 105 逆波兰表达式求值
有效的算符包括 +
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
思路
- 首先定义一个hash表,做加减乘除和数字的对应关系
- 然后遍历数组,如果在hash表中不存在这个。(也就是说,是数字),就push进栈,然后跳出这轮循环
- 如果是加减乘除的符号。就进行运算。
- 最后返回运算后的结果
/**
* @param {string[]} tokens
* @return {number}
*/
var evalRPN = function(tokens) {
const s = new Map([
["+", (a, b) => a * 1 + b * 1],
["-", (a, b) => b - a],
["*", (a, b) => b * a],
["/", (a, b) => (b / a) | 0]
]);
const stack = [];
for (const i of tokens) {
if(!s.has(i)) {
stack.push(i);
continue;
}
stack.push(s.get(i)(stack.pop(),stack.pop()))
}
return stack.pop();
};
js除了Math.floor方法,还可以通过位运算|,>>实现向下取整。
x是必须的,并且是一个数值
Math.floor(x)
console.log(Math.floor(0.8));//输出0
console.log(Math.floor(-0.8));//输出-1
console.log(Math.floor(1));//输出1
console.log(Math.floor('d'));//如果不是数值,输出NaN
位运算符实现向下取整
console.log(0.8 | 0);//输出0
console.log(-0.8 | 0);//输出0
console.log(1 | 0);//输出1
或者使用>>
console.log(0.8 >> 0);//输出0
console.log(-0.8 >> 0);//输出0
console.log(1 >> 0);//输出1
位运算和Math.floor()的差别
var a=(Math.pow( 2,32)/2-1)+0.5;//a=2147483647.5
a | 0;
a >> 0;
Math.floor(a);
//结果都为2147483647
a+=1;
a | 0;//输出-2147483648
a >> 0;//输出-2147483648
Math.floor(a);//输出2147483648
当值为负数的时候。位运算和Math.floor不一致
//只是由于它先进行转为32位的整数,再在进行舍去小数位,最后转变为负数的结果。
console.log(Math.floor(-0.8));//输出-1
console.log(-0.8 | 0);//输出0
console.log(-0.8 >> 0);//输出0
6. 滑动窗口最大值
- leetCode 239 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
输入: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
思路
解法一:遍历,push进窗口,然后当长度达到k,就删除第一个元素,再push进去。会超时
var maxSlidingWindow = function(nums, k) {
let result = []
let window = []
for (let i = 0; i < nums.length + 1; i++) {
if (window.length == k) {
result.push(Math.max(...window))
window.shift()
}
window.push(nums[i])
}
return result
}
console.log(maxSlidingWindow([1, -1], 1))
解法二:
7. 前K个高频元素
- leetCode 347前 K 个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
思路
使用一个hash表来存储出现的次数,然后从大到小排序,再然后前k个
var topKFrequent = function(nums, k) {
let map = new Map()
let arr = [...new Set(nums)]
for (let i = 0; i < nums.length; i++) {
map.set(nums[i], map.has(nums[i]) ? map.get(nums[i]) + 1 : 1)
}
let res = []
res = arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k)
return res
}
let result = topKFrequent([1, 1, 1, 2, 2, 3], 2)
console.log(result)