栈与队列
225.用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x)将元素 x 压入栈顶。int pop()移除并返回栈顶元素。int top()返回栈顶元素。boolean empty()如果栈是空的,返回true;否则,返回false。
注意:
- 你只能使用队列的基本操作 —— 也就是
push to back、peek/pop from front、size和is empty这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
解答思路:
(1) 由于此题不涉及算法,所以我们只需要认真考虑两种数据结构的区别。
栈:后进先出。
队列:先进先出。
用队列模拟栈,首先我们得有一个队列。
由于队列和栈的特性队列中最后一个元素就是栈顶元素,所以基于栈的操作用队列模拟,我们有两种做法:
- 依次出队,并将出队元素添加至队尾,直至最后一个元素到队头,一个对列即可实现
- 出对,将出队数据保存到一个新的队列,直至旧的队列中只有一个元素。
由上面两种思路我们已经做到了队列模拟栈的基本逻辑。接下来我们来看代码。
// 第一种做法
var MyStack = function () {
this.queue = []
};
/**
* @param {number} x
* @return {void}
*/
MyStack.prototype.push = function (x) {
this.queue.push(x)
};
/**
* @return {number}
*/
MyStack.prototype.pop = function () {
let size = this.queue.length
// 当队列中只剩下队尾时,就是栈顶元素
while (size-- > 1) {
this.queue.push(this.queue.shift())
}
return this.queue.shift()
};
/**
* @return {number}
*/
MyStack.prototype.top = function () {
// 这个位置为了避免重复逻辑直接调用pop方法获得栈顶
const x = this.pop()
this.queue.push(x)
return x
};
/**
* @return {boolean}
*/
MyStack.prototype.empty = function () {
return !this.queue.length
};
// 第二种做法
var MyStack = function () {
this.queue1 = []
this.queue2 = []
};
/**
* @param {number} x
* @return {void}
*/
MyStack.prototype.push = function (x) {
this.queue1.push(x)
};
/**
* @return {number}
*/
MyStack.prototype.pop = function () {
if (!this.queue1.length) {
[this.queue1, this.queue2] = [this.queue2, this.queue1]
}
while (this.queue1.length > 1) {
this.queue2.push(this.queue1.shift())
}
return this.queue1.shift()
};
/**
* @return {number}
*/
MyStack.prototype.top = function () {
const x = this.pop()
this.queue1.push(x)
return x
20.有效括号 ~ 1047
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
思路:
(1)本题是一个匹配问题,匹配问题可以用栈来解决,我们先想一想什么情况是不匹配的。
(2)思考不匹配情况。
-
遍历完发现栈不为空
-
遍历到中间发现类型不匹配。
(3)抽象算法
- 左括号进栈,右括号匹配栈。不匹配,return false.
- 最后判断栈是否为空,为空true, 否则false
(4) 书写代码
方法1:
//这里匹配到左括号时进入右括号,是为了简便运算,我们只需要把括号理解为一个标记这个标记可以有由自己定。
var isValid = function (s) {
const stack = []
for (let i = 0; i < s.length; i++) {
const c = s[i]
switch (c) {
case '[':
stack.push(']')
break
case '{':
stack.push('}')
break
case '(':
stack.push(')')
break
default:
if (stack.pop() !== c) {
return false
}
}
}
return stack.length === 0
};
方法2:
// 做一个映射标,可以很快的匹配
var isValid = function (s) {
const map = {
'{': '}',
'[': ']',
'(': ')'
}
const stack = []
for (let i of s) {
if (i in map) {
stack.push(i)
continue
}
if (map[stack.pop()] !== i) return false
}
return !stack.length
};
150.逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
思路:
(1)首先我们要考虑后缀表达式的算法流程,因该是匹配到一个符号就要开始计算一个值,然后把这个值保存起来循环往复,这个情况可以使用栈结构,匹配到符号就出栈计算一次。
(2)考虑到有四个符号去匹配不同算法,我们可以使用键值对的方式进行对号入座,例如:”+“:(a, b) => a + b
(3)抽象算法。
- 遍历tokens, 如果是符号就取出栈顶元素计算,并把计算的值进入栈,不是就该元素push进入栈。
- 最后返回栈顶元素。
(4)书写代码
var evalRPN = function(tokens) {
const stack = []
// 构造映射关系
const map = new Map([
['+', (a, b) => a*1 + b*1],
['*', (a, b) => a*b],
['-', (a, b) => a - b],
['/', (a, b) => a / b]
])
for (let i of tokens) {
if (map.has(i)) {
const a = stack.pop()
const b = stack.pop()
stack.push(parseInt(map.get(i)(b, a))) // 取整
} else {
stack.push(i)
}
}
return stack.pop()
};