跟着卡哥学算法Day 10:栈与队列理论 & 常见题目
正在加载今日诗词....2025-02-21
JavaScript 没有独立的数据结构,但可通过数组模拟,或自行封装类。
栈 LIFO
后进先出:最后添加的元素最先被移除
操作
- push:元素入栈(添加到栈顶)
- pop:元素出栈(移除栈顶元素)
- peek:查看栈顶元素(不移除)
实现方式
使用数组实现
const stack = [] stack.push(1) // 入栈 [1] stack.push(2) // 入栈 [1, 2] const top = stack.pop() // 出栈 2,栈变为 [1]
定义 Stack 类
class Stack { constructor() { this.items = [] } push(item) { this.items.push(item) } pop() { return this.items.pop() } peek() { return this.items[this.items.length - 1] } isEmpty() { return this.items.length === 0 } } const s = new Stack() s.push(10) s.push(20) s.pop() // 返回 20
队列 FIFO
先进先出:最先添加的元素最先被移除
操作
- enqueue:元素入队(添加到队尾)
- dequeue:元素出队(移除队首元素)
- front:查看队首元素(不移除)
实现方式
使用数组实现
const queue = [] queue.push(1) // 入队 [1] queue.push(2) // 入队 [1, 2] const front = queue.shift() // 出队 1,队列变为 [2]
性能较差,因为shift()操作会导致所有元素前移,时间复杂度是 O(n),频繁操作可能影响性能
定义 Queue 类
class Queue { constructor() { this.items = {} this.frontIndex = 0 // 队首指针 this.rearIndex = 0 // 队尾指针 } enqueue(item) { this.items[this.rearIndex] = item this.rearIndex++ } dequeue() { if (this.isEmpty()) return null const item = this.items[this.frontIndex] delete this.items[this.frontIndex] this.frontIndex++ return item } front() { return this.items[this.frontIndex] } isEmpty() { return this.rearIndex === this.frontIndex } } const q = new Queue() q.enqueue(10) q.enqueue(20) q.dequeue() // 返回 10
通过指针追踪队首和队尾,性能更优
接下来都通过 🌟 来代表题目难度:
- 简单:🌟
- 中等:🌟🌟
- 困难:🌟🌟🌟
经典题目
232.用栈实现队列 🌟
题目描述
使用栈实现队列的下列操作:
- push(x) -- 将一个元素放入队列的尾部。
- pop() -- 从队列首部移除元素。
- peek() -- 返回队列首部的元素。
- empty() -- 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
- 你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
- 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
解题思路
队列先进先出,如 1 -> 2 -> 3
,3 先进队列,同样也是先出队列
如果想用栈来模拟队列,需要两个栈来实现
- 进栈:将元素压入输入栈
- 出栈:如果输出栈不为空,则直接从栈顶弹出;为空时,需要将输入栈所有元素压入输出栈,再从输出栈弹出数据
- 如果两个栈都为空,则模拟队列为空
代码
var MyQueue = function () {
this.stackIn = []
this.stackOut = []
}
/**
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function (x) {
this.stackIn.push(x)
}
/**
* @return {number}
*/
MyQueue.prototype.pop = function () {
if (this.stackOut.length) {
return this.stackOut.pop()
}
while (this.stackIn.length) {
this.stackOut.push(this.stackIn.pop())
}
return this.stackOut.pop()
}
/**
* @return {number}
*/
MyQueue.prototype.peek = function () {
const x = this.pop()
this.stackOut.push(x)
return x
}
/**
* @return {boolean}
*/
MyQueue.prototype.empty = function () {
return !this.stackIn.length && !this.stackOut.length
}
225. 用队列实现栈 🌟
力扣链接 🌟
题目描述
使用队列实现栈的下列操作:
- push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
注意:
- 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
- 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
- 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
解题思路
类比用栈实现队列,使用两个栈来实现,那么此处也可以用两个队列来实现栈
如何用一个队列实现栈?
- 入队列:添加元素进队列
- 出队列:获取队列长度,将对首元素重新入队列,直到只剩最后一个元素
- 如果队列为空,则模拟栈为空
代码
// 使用set解决
var intersection = function (nums1, nums2) {
const set1 = new Set(nums1)
const result = new Set()
for (let i of nums2) {
if (set1.has(i)) {
result.add(i)
}
}
return [...result]
}
// 使用数组解决
var intersection = function (nums1, nums2) {
const record = new Array(1000).fill(0)
const result = new Set()
for (let i of nums1) {
record[i] = 1
}
for (let i of nums2) {
if (record[i] === 1) result.add(i)
}
return [...result]
}
- 用 set 作为哈希表时,每次 insert 值时都需要对这个值做哈希运算,转变为另一个内部存储的值,同时需要开辟新的空间存储,相对来说需要花费时间更多。
- 用数组作为哈希表效率高,用下标做哈希映射,速度是最快的
20. 有效的括号 🌟
力扣链接 🌟
题目描述
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
输入: "()[]{}"
输出: true
输入: "([)]"
输出: false
解题思路
栈结构非常适合做匹配类的题目
- 遇到左括号,则入栈
- 遇到右括号,则判断栈顶元素是否匹配,匹配则出栈,否则返回 false
- 循环结束后,栈为空,则返回 true
代码
var isValid = function (s) {
const map = new Map()
map.set('(', ')')
map.set('[', ']')
map.set('{', '}')
const arr = Array.from(s)
let result = []
for (let i of arr) {
const right = map.get(i)
if (right) {
result.unshift(i)
continue
}
if (map.get(result.shift()) !== i) {
return false
}
}
return !result.length
}
1047. 删除字符串中的所有相邻重复项 🌟
力扣链接 🌟
题目描述
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca"
输出:"ca"
解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
提示:
- 1 <= S.length <= 20000
- S 仅由小写英文字母组成。
解题思路
遍历字符串数组,如果当前字符等于栈顶元素,出栈,否则入栈
代码
var removeDuplicates = function (s) {
const arr = Array.from(s)
const result = []
for (let i of arr) {
if (result[result.length - 1] !== i) {
result.push(i)
} else {
result.pop()
}
}
return result.join('')
}
京ICP备2022027737号
Copyright © 2022 - present @wangxiang