算法随笔-数据结构(栈)

本文主要介绍数据结构中的栈的特点、使用场景、ES6实现Stack类和题解leetCode真题。供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

这个名字对开发者来说不应该陌生,我们经常说调用栈、堆栈等术语,指的就是数据结构-。栈是一种非常基础的数据结构,在计算机科学和软件开发的许多领域中都有广泛的应用。

栈(Stack)是一种遵循后进先出LIFO,Last In First Out)原则的数据结构。这意味着最后添加到栈中的元素将是第一个被移除的元素。栈的这个特性使得它在许多应用场景中非常有用,尤其是在处理数据的顺序和层次结构时。

栈的主要特点

  1. 限制性访问:只能在栈的顶部(称为栈顶)进行添加(push)和删除(pop)操作。
  2. 主要操作
  • push:向栈顶添加一个新元素。
  • pop:移除栈顶的元素,并返回它。
  • peektop:查看栈顶的元素,但不从栈中移除它。
  • isEmpty:检查栈是否为空。
  1. 容量:有些栈实现有固定的最大容量,而有些可以实现动态扩展。
  2. 时间效率:栈的基本操作(push、pop、peek)通常都是O(1)时间复杂度,即它们可以在常数时间内完成。

栈的应用场景

  1. 函数调用:编程语言中的函数调用通常使用调用栈来管理,每次函数调用时,相关信息(如返回地址和局部变量)被压入栈中,函数执行完毕后,这些信息被弹出。
  2. 撤销操作:许多应用程序(如文本编辑器)提供撤销功能,可以使用栈来存储操作历史,以便可以回退到之前的状态。
  3. 解析表达式:栈用于解析和计算表达式,特别是后缀和前缀表达式。
  4. 深度优先搜索(DFS):在图的遍历中,DFS 使用栈来存储待访问的节点。
  5. 括号匹配:检查代码中的括号是否正确匹配,可以使用栈来跟踪最近遇到的开括号。
  6. 回溯算法:在解决一些问题时(如迷宫求解N皇后问题等),回溯算法会用栈来存储中间状态。
  7. 数据结构辅助:栈可以作为其他数据结构(如队列的实现)的辅助结构使用。
  8. 内存管理:在一些低级编程中,栈可能会用于管理内存分配和回收。

栈是一种非常基础的数据结构,在计算机科学和软件开发的许多领域中都有广泛的应用。

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. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例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;
};