栈与队列:实现与经典应用
本节目标
- 掌握栈的"后进先出"与队列的"先进先出"
- 会用单调栈解决"下一个更大元素"类问题
- 掌握循环队列与优先队列的思想
栈(LIFO)与队列(FIFO):
// 运行环境:Node.js 14+
// 保存为 dsa-l6.js,执行:node dsa-l6.js
// 栈:数组 push/pop 即可(O(1) 均摊)
const stack = []
stack.push(1); stack.pop()
// 队列别用 shift(O(n))!用"头指针"实现 O(1) 队列:
function createQueue() {
const q = []
let head = 0
return {
enqueue(x) { q.push(x) },
dequeue() { return head < q.length ? q[head++] : undefined },
size() { return q.length - head },
isEmpty() { return head >= q.length }
}
}栈的经典应用:有效括号、逆波兰表达式、函数调用栈:
// 有效括号:遇到左括号入栈,右括号弹栈匹配
function isValid(s) {
const map = { ')': '(', ']': '[', '}': '{' }
const st = []
for (const ch of s) {
if (ch === '(' || ch === '[' || ch === '{') st.push(ch)
else if (st.pop() !== map[ch]) return false
}
return st.length === 0
}
console.log(isValid('()[]{}')) // true单调栈(Monotonic Stack)——"下一个更大元素"的标准解法:维护"栈内元素单调"的栈,入栈时弹出破坏单调性的元素。
function nextGreater(nums) {
const res = new Array(nums.length).fill(-1)
const st = [] // 存下标,栈底→栈顶 递减
for (let i = 0; i < nums.length; i++) {
while (st.length && nums[st[st.length - 1]] < nums[i]) {
res[st.pop()] = nums[i] // 弹出的元素遇到"下一个更大"
}
st.push(i)
}
return res
}
console.log(nextGreater([2, 1, 2, 4, 3])) // [4, 2, 4, -1, -1]循环队列:固定大小数组 + 头尾指针取模,避免扩容,常用于流式处理。优先队列:按优先级出队,底层是堆(第四章详讲);JS 无内置,需手写或用库。
名词解释
- 栈(Stack,LIFO):后进先出,像一摞盘子,只能从顶上放/取。核心操作
push(压栈)、pop(弹栈)、peek(看栈顶)。典型应用:括号匹配、撤销操作、函数调用栈。 - 队列(Queue,FIFO):先进先出,像排队买票,先来先走。核心操作
enqueue(入队)、dequeue(出队)。注意 JS 数组shift是 O(n),手写队列要靠头指针。 - 单调栈(Monotonic Stack):栈内元素保持单调递增或递减。入新元素时不断弹出"被它打破单调"的老元素,从而高效求出"左边/右边第一个更大/更小"的问题。
课后练习
- 有效括号(leetcode 20)的关键点?
- 答案:用栈:遇左括号压栈;遇右括号弹栈比对是否匹配;最后栈必须空(防止
"("这种情况)。上面isValid即标准解。
- 答案:用栈:遇左括号压栈;遇右括号弹栈比对是否匹配;最后栈必须空(防止
- 每日温度(单调栈,739)怎么套模板?
- 答案:维护"递减栈"存下标,当遇到更高温度
t[i]时,弹栈并把res[弹出的下标] = i - 弹出的下标(等待天数)。与nextGreater同构。
- 答案:维护"递减栈"存下标,当遇到更高温度
总结
栈和队列是所有高级数据结构的基石,理解它们关键在于抓住"进出顺序"这个灵魂:栈是后进先出,天然适合"配对/撤销/嵌套"类问题;队列是先进先出,天然适合"排队/广度遍历"类问题。很多人不知道的是,JS 里用数组当队列直接 shift 是性能陷阱——它是 O(n),正确姿势是用头指针或 Deque。单调栈则是把"找下一个更大/更小元素"这类看似困难的问题,化成一个优雅的模板:维护单调性,入栈即结算。我特别想强调"栈"在真实工程里的存在感:你写的每一个函数调用、每一次浏览器后退、每一回 Ctrl+Z 撤销,底层都是栈。学会它,不只是为了刷题,更是为了看懂程序是怎么"记住来路"的。