Day10-D11-栈与队列,leetcode232,225,20,1047
栈与队列
理论
- 栈,先进后出
-
栈方法
- push,添加元素
- pop,移除元素
- peek,查看栈顶
- isEmpty,检查空状态
- size,获取大小
- clear,清空
-
由于栈结构的特殊性,非常适合做对称匹配类的题目。
// 基于数组实现队列
class Stack {
constructor() {
this.items = [];
}
// JavaScript 数组的 push() 和 pop() 方法时间复杂度都是 O(1)
// 入栈
push(element) {
this.items.push(element);
}
// 出栈
pop() {
if (this.isEmpty()) return undefined;
return this.items.pop();
}
// 查看栈顶元素
peek() {
if (this.isEmpty()) return undefined;
return this.items[this.items.length - 1];
}
// 检查是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取栈大小
size() {
return this.items.length;
}
// 清空栈
clear() {
this.items = [];
}
}
- 队列,先进先出
- 队列方法
- enqueue,添加元素
- dequeue,移除元素
- front / peek,查看顶部
- isEmpty,检查空状态
- size,获取大小
- clear,清空
// 基于数组实现,不推荐
class Queue {
constructor() {
this.items = [];
}
// 入队
enqueue(element) {
this.items.push(element);
}
// 出队
dequeue() {
if (this.isEmpty()) return undefined;
return this.items.shift();
}
// 查看队首元素
front() {
if (this.isEmpty()) return undefined;
return this.items[0];
}
// 检查是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取队列大小
size() {
return this.items.length;
}
// 清空队列
clear() {
this.items = [];
}
}
// 问题:shift() 操作的时间复杂度是 O(n),因为需要移动所有剩余元素
- 优化的队列实现
// 方案1:使用对象和指针
class OptimizedQueue {
constructor() {
this.items = {};
this.frontIndex = 0;
this.rearIndex = 0;
}
enqueue(element) {
this.items[this.rearIndex] = element;
this.rearIndex++;
}
dequeue() {
if (this.isEmpty()) return undefined;
const item = this.items[this.frontIndex];
delete this.items[this.frontIndex];
this.frontIndex++;
return item;
}
// 其他方法类似...
}
// 方案2:使用链表实现
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedListQueue {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
enqueue(value) {
const newNode = new Node(value);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
}
dequeue() {
if (!this.head) return undefined;
const value = this.head.value;
this.head = this.head.next;
this.length--;
if (!this.head) this.tail = null;
return value;
}
// 其他方法...
}
题目
- 用栈实现队列
-
使用栈实现队列的下列操作:
-
push(x) -- 将一个元素放入队列的尾部。
-
pop() -- 从队列首部移除元素。
-
peek() -- 返回队列首部的元素。
-
empty() -- 返回队列是否为空。
-
思路:
-
需要利用两个栈,一个用来入栈,一个用来出栈,在push数据时,只要把数据放进输入栈就好,但在pop时,输出栈如果为空,就把进栈所有数据全部倒入进来到出栈,再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据。如果进栈和出栈都为空的话,说明模拟的队列为空了。
var MyQueue = function() {
this.stackIn = []
this.stackOut = []
}
MyQueue.prototype.push = function(x) {
this.stackIn.push(x)
}
MyQueue.prototype.pop = function() {
const size = this.stackOut.length
if (size) {
return this.stackOut.pop()
}
while(this.stackIn.length) {
this.stackOut.push(this.stackIn.pop())
}
return this.stackOut.pop()
}
MyQueue.prototype.peek = function() {
// 先调用 pop() 方法弹出队首元素(即模拟队列的第一个元素)。
const x = this.pop()
// 因为 peek 只是“查看”队首元素,不应该真的移除它,所以把刚刚弹出的元素又放回 stackOut 栈顶,保证队列状态不变。
this.stackOut.push(x)
// 返回队首元素的值。
return x
}
MyQueue.empty = function() {
return !this.stackIn.length && !this.stackOut.length
}
- 用队列实现栈
-
使用队列实现栈的下列操作:
-
push(x) -- 元素 x 入栈
-
pop() -- 移除栈顶元素
-
top() -- 获取栈顶元素
-
empty() -- 返回栈是否为空
-
思路
-
队列先进先出,栈先进后出,同样123存放于队列和栈栈,栈会先弹出3,而队列会先弹出1,为了使队列实现栈的功能,即队列也弹出1,可以考虑队列长度len,然后先弹出len-1长度的数据,再重新加入队列,再弹出来,即是最后一个元素被弹出,
// 使用一个队列实现
var MyStack = function() {
// 用一个数组 queue 作为队列,来模拟栈的操作
this.queue = [];
};
MyStack.prototype.push = function(x) {
// 直接把元素加入队列尾部。
this.queue.push(x);
};
// 通过 while 循环,把队列前面的 size-1 个元素依次移到队尾。
// 这样原本最后加入的元素(栈顶)就被移到了队首。
// 最后 shift() 弹出队首元素,相当于弹出栈顶。
MyStack.prototype.pop = function() {
let size = this.queue.length;
while(size-- > 1) {
this.queue.push(this.queue.shift());
}
return this.queue.shift();
};
// 先用 pop() 得到栈顶元素(并移除)。
// 再把这个元素重新放回队尾,保证队列状态不变。
// 返回这个元素。
MyStack.prototype.top = function() {
const x = this.pop();
this.queue.push(x);
return x;
};
// 判断队列是否为空。
MyStack.prototype.empty = function() {
return !this.queue.length;
};
- 有效的括号
-
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
-
有效字符串需满足:
-
左括号必须用相同类型的右括号闭合。
-
左括号必须以正确的顺序闭合。
-
注意空字符串可被认为是有效字符串。
-
思路:
-
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
-
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
-
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
-
字符串遍历完之后,栈是空的,就说明全都匹配了。
-
遍历字符串,遇到左括号就往栈里放右括号(为了后面遍历到右括号时方便做比较),遇到右括号,要去栈里去匹配,找字符串前面是否出现对应的左括号,如果栈为空或者如果栈顶的元素不等于当前遍历的元素,说明不匹配。若遍历完字符串后,若栈不为空,即情况1,多做括号,返回false不匹配,
var isValid = function (s) {
const stack = [];
for (let i = 0; i < s.length; i++) {
let c = s[i];
switch (c) {
case '(':
stack.push(')');
break;
case '[':
stack.push(']');
break;
case '{':
stack.push('}');
break;
default:
if (c !== stack.pop()) {
return false;
}
}
}
return stack.length === 0;
};
var isValid = function(s) {
const stack = [],
map = {
"(":")",
"{":"}",
"[":"]"
};
for(const x of s) {
if(x in map) {
stack.push(x);
continue;
};
if(map[stack.pop()] !== x) return false;
}
return !stack.length;
};
- 删除字符串中的所有相邻重复项
-
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
-
在 S 上反复执行重复项删除操作,直到无法继续删除。
-
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
-
思路:
-
在删除相邻重复项的时候,需要知道当前遍历的这个元素,在前一位是不是遍历过一样数值的元素,那么就需要记录前面遍历过的元素。
-
可以用栈来存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。然后再去做对应的消除操作。从栈中弹出剩余元素
-
遍历完后,栈中的元素顺序就是最终结果的顺序(因为每次都是从左到右处理,栈底是字符串开头,栈顶是结尾)。
// 使用栈
var removeDuplications = function(s) {
const result = []
for (const val of s) {
if (val === result[result.length - 1]) {
// 如果当前字符和栈顶元素相同,则弹出栈顶(消除一对重复项)
result.pop()
} else {
// 否则入栈
result.push(val)
}
}
// 栈中剩余元素即为最终结果
return result.join('')
}