算法随笔-数据结构(栈)
本文主要介绍数据结构中的栈的特点、使用场景、ES6实现Stack类和题解leetCode真题。供自己以后查漏补缺,也欢迎同道朋友交流学习。
引言
栈
这个名字对开发者来说不应该陌生,我们经常说调用栈、堆栈等术语,指的就是数据结构-栈
。栈是一种非常基础的数据结构,在计算机科学和软件开发的许多领域中都有广泛的应用。
栈(Stack
)是一种遵循后进先出
(LIFO
,Last In First Out)原则的数据结构。这意味着最后添加到栈中的元素将是第一个被移除的元素。栈的这个特性使得它在许多应用场景中非常有用,尤其是在处理数据的顺序和层次结构时。
栈的主要特点
- 限制性访问:只能在栈的顶部(称为栈顶)进行添加(push)和删除(pop)操作。
- 主要操作:
push
:向栈顶添加一个新元素。pop
:移除栈顶的元素,并返回它。peek
或top
:查看栈顶的元素,但不从栈中移除它。isEmpty
:检查栈是否为空。
- 容量:有些栈实现有固定的最大容量,而有些可以实现动态扩展。
- 时间效率:栈的基本操作(push、pop、peek)通常都是O(1)时间复杂度,即它们可以在常数时间内完成。
栈的应用场景
- 函数调用:编程语言中的函数调用通常使用
调用栈
来管理,每次函数调用时,相关信息(如返回地址和局部变量)被压入栈中,函数执行完毕后,这些信息被弹出。 - 撤销操作:许多应用程序(如文本编辑器)提供
撤销功能
,可以使用栈来存储操作历史
,以便可以回退到之前的状态。 - 解析表达式:栈用于解析和计算表达式,特别是后缀和前缀表达式。
- 深度优先搜索(DFS):在图的遍历中,
DFS
使用栈来存储待访问的节点。 - 括号匹配:检查代码中的括号是否正确匹配,可以使用栈来跟踪最近遇到的开括号。
- 回溯算法:在解决一些问题时(如
迷宫求解
、N皇后
问题等),回溯算法会用栈来存储中间状态。 - 数据结构辅助:栈可以作为其他数据结构(如队列的实现)的辅助结构使用。
- 内存管理:在一些低级编程中,栈可能会用于管理内存分配和回收。
栈是一种非常基础的数据结构,在计算机科学和软件开发的许多领域中都有广泛的应用。
ES6实现栈类
在 ES6
中,可以使用数组来实现栈的数据结构,因为数组本身具有类似栈的操作功能,如 push
用于添加元素到栈顶,pop
用于移除栈顶元素。以下是使用JavaScript数组实现栈的一个简单示例:
class Stack {
constructor() {
this.items = []; // 使用数组存储栈中的元素
}
// 向栈中添加元素
push(element) {
this.items.push(element);
}
// 从栈中移除元素并返回
pop() {
if (this.isEmpty()) {
throw new Error('Stack is empty');
}
return this.items.pop();
}
// 查看栈顶元素但不移除
peek() {
if (this.isEmpty()) {
throw new Error('Stack is empty');
}
return this.items[this.items.length - 1];
}
// 检查栈是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取栈的大小
size() {
return this.items.length;
}
// 清空栈
clear() {
this.items = [];
}
}
// 使用示例
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop()); // 输出: 3
console.log(stack.peek()); // 输出: 2
console.log(stack.isEmpty()); // 输出: false
console.log(stack.size()); // 输出: 2
在这个Stack
类中,我们定义了以下方法:
push(element)
: 向栈中添加一个元素。pop()
: 移除并返回栈顶元素。如果栈为空,则抛出错误。peek()
: 返回栈顶元素但不移除它。如果栈为空,则抛出错误。isEmpty()
: 检查栈是否为空。size()
: 返回栈中元素的数量。clear()
: 清空栈中的所有元素。
LeetCode真题
1047. 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca"
输出:"ca"
解释: 例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
题解:
function removeDuplicates(s) {
var stack = [];
for ( v of s) {
let prev = stack.pop();
if (prev !== v) {
stack.push(prev);
stack.push(v);
}
}
return stack.join('');
}
// "abbaca"
// 第一次循环 stack = [] => [undefined, 'a']
// 第二次循环 stack = [undefined, 'a'] => [undefined, 'a', 'b']
// 第三次循环 stack = [undefined, 'a', 'b'] => [undefined, 'a']
// 第四次循环 stack = [undefined, 'a'] => [undefined]
// 第五次循环 stack = [undefined] => [undefined, 'c']
// 第六次循环 stack = [undefined, 'c'] => [undefined, 'c', 'a']
// 返回"ca"
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例1:
输入:s = "()"
输出:true
示例2:
输入:s = "()[]{}"
输出:true
示例3:
输入:s = "(]"
输出:false
题解:
var isValid = function(s) {
var stack = [];
for (let i = 0; i < s.length; i++) {
const start = s[i];
if (s[i] === '(' || s[i] === '{' || s[i] === '[') {
stack.push( s[i]);
} else {
const end = stack[stack.length - 1];
if (start === ')' && end === '(') {
stack.pop();
} else if (start === '}' && end === '{') {
stack.pop();
} else if (start === ']' && end === '[') {
stack.pop();
} else {
return false;
}
}
}
return stack.length === 0;
};