第三章-数据结构之栈
栈-简介
- 栈是一个后进先出的数据结构。
- 在 JavaScript 中没有栈这个数据结构,但是可以用 Array 实现栈的所有功能
const stack = [];
stack.push(1); // [1]
stack.push(2); // [1,2]
const item1 = stack.pop(); // item1 = 2 stack = [1]
const item2 = stack.pop(); // item2 = 1 stack = []
栈的应用场景
所有需要 后进先出 的场景都可以考虑使用栈这种数据结构
-
十进制转二进制
- 十进制转二进制中,后出来的余数反而要排到前面
- 把余数一次入栈,然后再出栈,就可以实现余数的倒序输出
-
判断字符串的括号是否有效闭合
- 越靠后的左括号,对应的右括号越靠前。
- 左括号入栈,右括号出栈,最后栈空了就是合法的
-
函数调用堆栈
- 最后调用的函数,最先执行完毕
- JS 解释器使用栈来控制函数的调用顺序
LeetCode 20题:有效的括号
原题链接
【题目描述】给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例:
输入: "()"
输出: true
输入: "()[]{}"
输出: true
输入: "(]"
输出: false
输入: "([)]"
输出: false
输入: "{[]}"
输出: true
【解题思路】
- 对于没有闭合的左括号而言,越靠后的左括号,对应的右括号越靠前
- 满足后进先出,考虑使用栈
【解题步骤】
- 新建一个栈
- 扫描字符串,遇到左括号入栈,遇到和栈顶括号类型匹配的右括号就出栈,类型不匹配直接判定为不合法
- 最后栈空了就合法,否则就不合法
【暴力解法】
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
// 如果字符串的长度为奇数就直接判定为 false
const len = s.length
if (len % 2 === 1) {
return false;
}
// 新建一个 ‘栈’
const stack = [];
for (let i = 0; i < len; i++) {
const current = s[i];
const stackTop = stack[stack.length - 1];
// 如果是左括号就入栈
if (current === '(' || current === '[' || current === '{') {
stack.push(current);
} else {
// 如果栈顶和第一个右括号类型相同就出栈
if (
(stackTop === '(' && current === ')') ||
(stackTop === '[' && current === ']') ||
(stackTop === '{' && current === '}')
) {
stack.pop();
} else {
// 否则直接返回 false
return false;
}
}
}
// 判断栈是否为空
return stack.length === 0
};
使用map优化
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
if (s.length % 2 === 1) {
return false;
}
const stack = [];
let map = new Map();
map.set('(', ')');
map.set('[', ']');
map.set('{', '}');
for (let i = 0; i < s.length; i++) {
let stackTop = stack[stack.length - 1];
if (map.has(s[i])) {
stack.push(s[i])
} else {
// map.get(s[i]) 获取右边括号的第一个值
if (s[i] === map.get(stackTop)) {
stack.pop();
} else {
return false;
}
}
}
return stack.length === 0;
};
LeetCode 144题:二叉树的前序遍历
【题目描述】
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
1
\
\
2
/
/
3
输入:root = [1,null,2,3]
输出:[1,2,3]
【解题思路】
利用栈的特性解题
- 新建一个 res 存放结果,stack 栈作为
- 前序遍历,左子树先访问,所以先将右子树进栈,再将左子树进栈
【答案】
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
const res = [];
const stack = [];
if (root) stack.push(root);
while(stack.length) {
let n = stack.pop();
res.push(n.val);
if (n.right) stack.push(n.right);
// 左子树 后进栈,先出栈
if (n.left) stack.push(n.left);
}
return res;
};
思考题
-
请用一个 ES6 的 class,封装一个 Stack 类,包括 push、pop、peek 方法。
class Stack { constructor() { this.stack = []; this.count = 0; } push (item) { this.stack = [...this.stack, item] this.count++ return this.stack } pop () { if (this.count === 0) return undefined; let remove = this.stack[this.count - 1]; delete this.stack[this.count - 1]; this.count-- return remove } peek () { if (this.count === 0) return undefined; return this.stack[this.count - 1]; } } // let stack = new Stack() // stack.push(1) // stack.push(2) // stack.push(3) // stack.push(4) // let res = stack.push(5) // console.log(res) // // stack.pop() // // stack.pop() // let res1 = stack.pop() // console.log(res1) // console.log(stack.peek())
-
请用栈这个数据结构,将十进制的 100 转为二进制数。
function toBinary (num) { let stack = []; let res = []; while (num > 0) { stack.push(num % 2);//1 1 num = Math.floor(num / 2); } // 在循环的时候,stack 的 length 会改变, // 因此要在for循环外部定义 stack 的长度 let len = stack.length; for (let i = 0; i < len; i++) { res.push(stack.pop()) } return res.join(''); } let res = toBinary(100); console.log(res); // 1100100