「栈」与「队列」是最常见的两个数据结构,并且在基础算法领域里也发挥着巨大的作用,应用十分广泛。
栈
「栈」是一种 后进先出 的数据结构,是一种人为规定的,只能在一端(栈顶)进行插入和删除操作,并且在栈非空的情况下,只能查看 栈顶 的元素的线性数据结构。
使用栈的例子:
- 进制的转换;
- 在计算机中表达式的计算,几乎都和栈有着密不可分的关系;
- 函数嵌套调用。嵌套调用(特殊的例子是递归)就是后面遇到的函数需要先执行;
- 在处理「树」和「图」结构的一些问题中,使用栈的算法叫做 深度优先遍历;
经典例题
简化路径 - 中等
function simplifyPath(path: string): string {
const pathArray = path.split('/')
const resultArray = []
pathArray.forEach((ele, index) => {
switch(ele) {
case '':
break
case '..':
resultArray.pop()
break
case '.':
break
default:
resultArray.push(ele)
}
})
return '/' + resultArray.join('/')
};
有效的括号 - 简单
function isValid(s: string): boolean {
if (s.length % 2 !== 0) return false
const sArray = s.split('')
const sStack = ['']
const sMap = {
')': '(',
']': '[',
'}': '{'
}
sArray.forEach(el => {
const stackTop = sStack.at(-1)
if (sMap[el] === stackTop) {
sStack.pop()
} else {
sStack.push(el)
}
})
return sStack.length === 1
};
逆波兰表达式求值 - 中等
/**
* @param {string[]} tokens
* @return {number}
*/
var evalRPN = function(tokens) {
const tokenArray = tokens
const resultStack = []
tokenArray.forEach(el => {
const next = resultStack.pop()
const prev = resultStack.pop()
switch(el) {
case '+':
resultStack.push(prev + next)
break
case '-':
resultStack.push(prev - next)
break
case '*':
resultStack.push(prev * next)
break
case '/':
resultStack.push((Math.trunc(prev / next)))
break
default:
resultStack.push(Number(el))
}
})
return resultStack.pop()
};
去除重复字母 - 中等
var removeDuplicateLetters = function (s) {
const arr = s.split("");
const sMap = new Map();
arr.forEach((el) => {
const cur = sMap.get(el);
if (cur) {
sMap.set(el, cur + 1);
} else {
sMap.set(el, 1);
}
});
const stack = [];
for (let i = 0; i < arr.length; i++) {
const cur = arr[i];
if (stack.includes(cur)) {
sMap.set(cur, sMap.get(cur) - 1);
continue;
}
while (
stack.length &&
stack.at(-1).charCodeAt() > cur.charCodeAt() &&
sMap.get(stack.at(-1))
) {
stack.pop();
}
stack.push(cur);
sMap.set(cur, sMap.get(cur) - 1);
}
return stack.join("");
};
不同字符的最小子序列 - 中等
解法和去除重复字母一样
单调栈
单调栈不是一个新的数据结构,单调栈就是普通的栈。对单调栈中元素的加入和取出依然要满足「后进先出 」原则。叫它单调栈是因为:在解决一些特定问题的过程中,栈中的元素在数值上「恰好保持单调性」。
用单调栈解决的问题的特点是:找出当前元素左边(或者右边)第1个比当前元素大或者小的那个元素。
经典例题
接雨水 - 困难
var trap = function (height) {
const len = height.length;
if (len < 3) return 0;
const stack = [];
let result = 0;
for (let i = 0; i < len; i++) {
const cur = height[i];
while (stack.length && cur > height[stack.at(-1)]) {
const bottom = stack.pop();
if (!stack.length) break;
const min = Math.min(cur, height[stack.at(-1)]);
const h = min - height[bottom];
const w = i - stack.at(-1) - 1;
result += h * w;
}
stack.push(i);
}
return result;
};
每日温度 - 中等
var dailyTemperatures = function (temperatures) {
const len = temperatures.length;
const result = new Array(len).fill(0);
const stack = [];
for (let i = 0; i < len; i++) {
const cur = temperatures[i];
while (stack.length && cur > temperatures[stack.at(-1)]) {
const index = stack.pop();
result[index] = i - index;
}
stack.push(i);
}
return result;
};
下一个更大元素I - 简单
var nextGreaterElement = function (nums1, nums2) {
const s2Map = new Map();
const stack = [];
for (let i = 0; i < nums2.length; i++) {
const cur = nums2[i];
while (stack.length && cur > nums2[stack.at(-1)]) {
s2Map.set(nums2[stack.pop()], cur);
}
stack.push(i);
}
return nums1.map((el) => s2Map.get(el) || -1);
};
下一个更大元素II - 中等
var nextGreaterElements = function(nums) {
const data = nums.concat(nums);
const len = data.length;
const result = new Array(nums.length).fill(-1);
const stack = [];
for (let i = 0; i < len; i++) {
while (stack.length && data[stack.at(-1)] < data[i]) {
if (result[stack.at(-1)]) {
result[stack.at(-1)] = data[i];
}
stack.pop();
}
stack.push(i);
}
return result;
};
柱状图中的最大矩形 - 困难
var largestRectangleArea = function (heights) {
const len = heights.length;
if (len === 1) return heights[0];
let max = 0;
const stack = [];
for (let i = 0; i < len; i++) {
const cur = heights[i];
while (stack.length && cur <= heights[stack.at(-1)]) {
const h = heights[stack.pop()];
let w;
if (stack.length) {
w = i - stack.at(-1) - 1;
} else {
w = i;
}
const area = w * h;
max = Math.max(area, max);
}
stack.push(i);
}
while (stack.length) {
const cur = heights[stack.pop()];
if (!stack.length) {
max = Math.max(max, cur * len);
} else {
max = Math.max(max, (len - stack.at(-1) - 1) * cur);
}
}
return max;
};
链表中的下一个更大节点 - 中等
var nextLargerNodes = function (head) {
const stack = [];
const result = [];
let count = 0;
while (head && head.val) {
const cur = head.val;
result[count] = 0;
while (stack.length && cur > stack.at(-1).value) {
result[stack.at(-1).index] = cur;
stack.pop();
}
stack.push({ index: count, value: cur });
head = head.next;
count++;
}
return result;
};
队列
「队列」是一种 先进先出 的数据结构。
使用队列的例子:
- 访问网页的时候,同一时间有大量的用户同时向一个页面发送请求,服务器会把一些来不及处理的任务放进一个队列里,一旦服务器系统里的处理线程完成了当前任务后,就会到队列里取走一个未处理的请求;
- 消息队列;
- 在处理「树」或者「图」结构的一些问题中,使用队列的算法叫做「广度优先遍历」;
经典例题
用栈实现队列 - 简单
var MyQueue = function() {
this.stack1 = []
this.stack2 = []
};
/**
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function(x) {
this.stack1.push(x)
};
/**
* @return {number}
*/
MyQueue.prototype.pop = function() {
const len = this.stack1.length
for (let i = 0; i < len - 1; i++) {
this.stack2.push(this.stack1.pop())
}
const result = this.stack1.pop()
while(this.stack2.length) {
this.stack1.push(this.stack2.pop())
}
return result
};
/**
* @return {number}
*/
MyQueue.prototype.peek = function() {
const len = this.stack1.length
for (let i = 0; i < len - 1; i++) {
this.stack2.push(this.stack1.pop())
}
const result = this.stack1.pop()
this.stack2.push(result)
while(this.stack2.length) {
this.stack1.push(this.stack2.pop())
}
return result
};
/**
* @return {boolean}
*/
MyQueue.prototype.empty = function() {
return !this.stack1.length
};
/**
* Your MyQueue object will be instantiated and called as such:
* var obj = new MyQueue()
* obj.push(x)
* var param_2 = obj.pop()
* var param_3 = obj.peek()
* var param_4 = obj.empty()
*/
用队列实现栈 - 简单
var MyStack = function () {
this.queue = [];
};
/**
* @param {number} x
* @return {void}
*/
MyStack.prototype.push = function (x) {
this.queue.push(x);
console.log("push", this.queue);
};
/**
* @return {number}
*/
MyStack.prototype.pop = function () {
const len = this.queue.length;
let count = 1;
while (count < len) {
const cur = this.queue.shift();
this.queue.push(cur);
count++;
}
return this.queue.shift();
console.log("pop", this.queue);
};
/**
* @return {number}
*/
MyStack.prototype.top = function () {
const len = this.queue.length;
let count = 1;
while (count < len) {
const cur = this.queue.shift();
this.queue.push(cur);
count++;
}
const top = this.queue.shift();
this.queue.push(top);
console.log("top", this.queue);
return top;
};
/**
* @return {boolean}
*/
MyStack.prototype.empty = function () {
return !this.queue.length;
};
/**
* Your MyStack object will be instantiated and called as such:
* var obj = new MyStack()
* obj.push(x)
* var param_2 = obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.empty()
*/
最小栈 - 中等
var MinStack = function () {
this.list = [];
this.min = [Infinity];
this.curMin = Number.MAX_SAFE_INTEGER;
};
/**
* @param {number} val
* @return {void}
*/
MinStack.prototype.push = function (val) {
this.list.push(val);
this.min.push(Math.min(this.min.at(-1), val));
};
/**
* @return {void}
*/
MinStack.prototype.pop = function () {
const pop = this.list.pop();
this.min.pop();
return pop;
};
/**
* @return {number}
*/
MinStack.prototype.top = function () {
return this.list.at(-1);
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function () {
return this.min.at(-1);
};
/**
* Your MinStack object will be instantiated and called as such:
* var obj = new MinStack()
* obj.push(val)
* obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.getMin()
*/
设计循环队列 - 中等
/**
* @param {number} k
*/
var MyCircularQueue = function (k) {
this.len = k + 1;
this.queue = new Array(this.len);
this.front = 0;
this.end = 0;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularQueue.prototype.enQueue = function (value) {
if (this.isFull()) {
return false;
}
this.queue[this.end] = value;
this.end = (this.end + 1) % this.len;
return true;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.deQueue = function () {
if (this.isEmpty()) {
return false;
}
this.front = (this.front + 1) % this.len;
return true;
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Front = function () {
if (this.isEmpty()) {
return -1;
}
return this.queue[this.front];
};
/**
* @return {number}
*/
MyCircularQueue.prototype.Rear = function () {
if (this.isEmpty()) {
return -1;
}
return this.queue[(this.end - 1 + this.len) % this.len];
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isEmpty = function () {
return this.end === this.front;
};
/**
* @return {boolean}
*/
MyCircularQueue.prototype.isFull = function () {
return (this.end + 1) % this.len === this.front;
};
/**
* Your MyCircularQueue object will be instantiated and called as such:
* var obj = new MyCircularQueue(k)
* var param_1 = obj.enQueue(value)
* var param_2 = obj.deQueue()
* var param_3 = obj.Front()
* var param_4 = obj.Rear()
* var param_5 = obj.isEmpty()
* var param_6 = obj.isFull()
*/
设计循环双端队列 - 中等
/**
* @param {number} k
*/
var MyCircularDeque = function (k) {
this.len = k + 1;
this.queue = new Array(this.len);
this.front = 0;
this.end = 0;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularDeque.prototype.insertLast = function (value) {
if (this.isFull()) {
return false;
}
this.queue[this.end] = value;
this.end = (this.end + 1) % this.len;
return true;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularDeque.prototype.insertFront = function (value) {
if (this.isFull()) {
return false;
}
this.front = (this.front - 1 + this.len) % this.len;
this.queue[this.front] = value;
return true;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.deleteFront = function () {
if (this.isEmpty()) {
return false;
}
this.front = (this.front + 1) % this.len;
return true;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.deleteLast = function () {
if (this.isEmpty()) {
return false;
}
this.end = (this.end - 1 + this.len) % this.len;
return true;
};
/**
* @return {number}
*/
MyCircularDeque.prototype.getFront = function () {
if (this.isEmpty()) {
return -1;
}
return this.queue[this.front];
};
/**
* @return {number}
*/
MyCircularDeque.prototype.getRear = function () {
if (this.isEmpty()) {
return -1;
}
return this.queue[(this.end - 1 + this.len) % this.len];
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.isEmpty = function () {
return this.front === this.end;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.isFull = function () {
return (this.end + 1) % this.len === this.front;
};
/**
* Your MyCircularDeque object will be instantiated and called as such:
* var obj = new MyCircularDeque(k)
* var param_1 = obj.insertFront(value)
* var param_2 = obj.insertLast(value)
* var param_3 = obj.deleteFront()
* var param_4 = obj.deleteLast()
* var param_5 = obj.getFront()
* var param_6 = obj.getRear()
* var param_7 = obj.isEmpty()
* var param_8 = obj.isFull()
*/
单调队列
单调队列就是普通的队列,只不过在使用队列的过程中,根据问题的特点保持了队列的单调性。
经典例题
滑动窗口的最大值 - 困难
var maxSlidingWindow = function (nums, k) {
const window = [];
const result = [];
const maxQueue = [];
for (let i = 0; i < nums.length; i++) {
const cur = nums[i];
window.push(cur);
if (!maxQueue.length) {
maxQueue.push(cur);
} else {
while (maxQueue.length && cur > maxQueue.at(-1)) {
maxQueue.pop();
}
maxQueue.push(cur);
}
if (window.length === k) {
// 需要去掉的窗口元素
const out = window.shift();
let curMax = maxQueue[0];
if (out === maxQueue[0]) {
maxQueue.shift();
}
result.push(curMax);
}
}
return result;
};
最后
参考文章: