当前位置: 首页 > news >正文

栈与队列-JS

一、基础知识

在JavaScript中,栈(Stack)和队列(Queue)是两种基本的数据结构,它们用于存储和操作数据。尽管JavaScript没有内置的栈和队列对象,但我们可以使用数组来模拟这两种数据结构的行为。

栈(Stack)

栈是一种后进先出(LIFO, Last In First Out)的数据结构。这意味着最后添加到栈中的元素将是第一个被移除的元素。

基本操作:

  • push(item):将一个元素添加到栈顶。
  • pop():移除并返回栈顶的元素。
  • peek():返回栈顶的元素,但不移除它。
  • isEmpty():检查栈是否为空。
  • size():返回栈中的元素数量。

使用数组模拟栈的示例:

let stack = [];

// 添加元素
stack.push('apple');
stack.push('banana');
stack.push('cherry');

// 移除并获取元素
let topItem = stack.pop(); // 'cherry'

// 查看栈顶元素
let peekItem = stack[stack.length - 1]; // 'banana'

// 检查是否为空
let isEmpty = stack.length === 0; // false

// 获取栈的大小
let size = stack.length; // 2

队列(Queue)

队列是一种先进先出(FIFO, First In First Out)的数据结构。这意味着第一个添加到队列中的元素将是第一个被移除的元素。

基本操作:

  • enqueue(item):在队列末尾添加一个元素。
  • dequeue():移除并返回队列前端的元素。
  • front() 或 peek():返回队列前端的元素,但不移除它。
  • isEmpty():检查队列是否为空。
  • size():返回队列中的元素数量。

使用数组模拟队列的示例:

let queue = [];

// 添加元素
queue.push('apple');
queue.push('banana');
queue.push('cherry');

// 移除并获取元素
let frontItem = queue.shift(); // 'apple'

// 查看队列前端元素
let peekItem = queue[0]; // 'banana'

// 检查是否为空
let isEmpty = queue.length === 0; // false

// 获取队列的大小
let size = queue.length; // 2

注意:在使用数组模拟队列时,shift() 方法用于移除数组的第一个元素,这可能会导致性能问题,因为需要移动数组中的所有其他元素。在需要高性能的场景下,可以使用双端队列(如 ArrayBuffer 与 TypedArray 结合使用)或其他数据结构来优化。

在JavaScript中,还可以使用类或对象来创建更正式的栈和队列实现,这样可以封装数据和行为,提供更清晰的接口

二、20.有效的括号

20. 有效的括号

 

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"

输出:true

示例 2:

输入:s = "()[]{}"

输出:true

示例 3:

输入:s = "(]"

输出:false

示例 4:

输入:s = "([])"

输出:true

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成
/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    const stack = [];
    let temp;
    //字符串转为数组
    s = s.split('');
    for (let i of s) {
        if (i === '(' || i === '{' || i === '[') {
            stack.push(i)
        } else {
            temp = stack.pop();
            if (i === ')') {
                if (temp !== '(') return false;
            }
            if (i === ']') {
                if (temp !== '[') return false;
            }
            if (i === '}') {
                if (temp !== '{') return false;
            }
        }
    }
    if(stack.length===0) return true;
    if(stack.length!==0) return false;

};

三、1047.删除字符串中的所有相邻重复项

1047. 删除字符串中的所有相邻重复项

 

提示

给出由小写字母组成的字符串 s重复项删除操作会选择两个相邻且相同的字母,并删除它们。

s 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

提示:

  1. 1 <= s.length <= 105
  2. s 仅由小写英文字母组成。

使用栈

/**
 * @param {string} s
 * @return {string}
 */
var removeDuplicates = function(s) {
    // 字符串转数组
    s=s.split('');
    let stack=[];
    for(let i of s){
        let temp=stack.pop()
        // 如果相等,就是出栈
        // 如果不相等,就是把出栈比较的元素也入栈,新元素也入栈。
        if(temp!==i){
            stack.push(temp);
            stack.push(i);
        }
    }
    return stack.join('');
};

更简单 ,字符串也可以直接通过下标读取每个元素,但是不能通过下标修改。

var removeDuplicates = function(s) {
    const result = []
    for(const i of s){
        if(i === result[result.length-1]){
            result.pop()
        }else{
            result.push(i)
        }
    }
    return result.join('')
};

四、150.逆波兰表达式求值

150. 逆波兰表达式求值

 

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 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 <= tokens.length <= 104
  • tokens[i] 是一个算符("+""-""*""/"),或是在范围 [-200, 200] 内的一个整数

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 )
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * )

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

 注意点:

  1. 类型转换:在处理运算时,从栈中弹出的元素是字符串类型,需要转换为数字类型进行运算。讲字符转为数组parseInt() Number()也可以
  2. 除法处理:JavaScript 中除法可能导致浮点数,而通常 RPN 的结果需要是整数。应当对结果进行取整处理。
  3. 返回值:最后返回的结果应该是数字类型。

parseInt() 和 Number() 都是 JavaScript 中用于将字符串转换为数字的函数,但它们在处理转换时有一些区别:

  1. 解析方式

    • parseInt():解析字符串中的整数部分。如果字符串开头不是数字或符号,则返回 NaN。它可以接受第二个参数作为基数(radix),用于指定数字的进制。
    • Number():尝试将整个字符串转换为数字。如果字符串包含任何非数字字符(除了小数点和指数符号),则返回 NaN
  2. 处理非数字字符

    • parseInt():会从字符串的开始位置解析数字,直到遇到第一个非数字字符为止。例如,parseInt('123abc') 会返回 123
    • Number():要求整个字符串必须是有效的数字表示,否则返回 NaN。例如,Number('123abc') 会返回 NaN
  3. 基数(Radix)

    • parseInt():可以指定基数,例如 parseInt('1010', 2) 会将二进制的 1010 转换为十进制的 10
    • Number():不接收基数参数,总是将字符串视为十进制数。
  4. 空字符串

    • parseInt():对于空字符串,返回 NaN
    • Number():对于空字符串,返回 0
  5. 性能

    • parseInt():通常比 Number() 快,因为它不需要解析整个字符串,只要找到第一个非数字字符就可以停止解析。
    • Number():需要检查整个字符串,以确保没有非法字符。
  6. 特殊值

    • parseInt():可以解析以 0x 开头的十六进制字符串,例如 parseInt('0x10') 会返回 16
    • Number():不会将十六进制字符串转换为十进制数,例如 Number('0x10') 会返回 NaN

在实际使用中,选择 parseInt() 还是 Number() 取决于你的具体需求。如果你只需要字符串中的整数部分,或者需要处理特定进制的数字,parseInt() 是更好的选择。如果你需要确保整个字符串是一个有效的数字,那么 Number() 更合适。

if+parseInt

/**
 * @param {string[]} tokens
 * @return {number}
 */
var evalRPN = function (tokens) {
    let stack = [];
    let temp, num1, num2;
    for (let i of tokens) {
        if (i === '+' || i === '-' || i === '*' || i === '/') {
            // 栈的最后一个元素
            num1 = parseInt(stack.pop());
            // 栈的倒数第二个元素
            num2 = parseInt(stack.pop());
            if (i === '+') {
                temp = num1 + num2;
            }
            if (i === '-') {
                temp = num2 - num1;
            }
            if (i === '*') {
                temp = num2 * num1;
            }
            if (i === '/') {
                temp = Math.trunc(num2 / num1);
            }
            // 计算结果入栈
            stack.push(temp);
        } else {
            stack.push(parseInt(i))
        }
    }
    return stack.pop();

}

 switch+Number

var evalRPN = function (tokens) {
    const stack = [];
    for (const token of tokens) {
        if (isNaN(Number(token))) { // 非数字
            const n2 = stack.pop(); // 出栈两个数字
            const n1 = stack.pop();
            switch (token) { // 判断运算符类型,算出新数入栈
                case "+":
                    stack.push(n1 + n2);
                    break;
                case "-":
                    stack.push(n1 - n2);
                    break;
                case "*":
                    stack.push(n1 * n2);
                    break;
                case "/":
                    stack.push(n1 / n2 | 0);
                    break;
            }
        } else { // 数字
            stack.push(Number(token));
        }
    }
    return stack[0]; // 因没有遇到运算符而待在栈中的结果
};

五、239.滑动窗口最大值

239. 滑动窗口最大值

尝试过

困难

相关标签

相关企业

提示

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

方法一、暴力法(超时了)

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
function maxSlidingWindow(nums, k) {
    const result = [];
    for (let i = 0; i <= nums.length - k; i++) {
        let max = -Infinity;
        for (let j = i; j < i + k; j++) {
            max = Math.max(max, nums[j]);
        }
        result.push(max);
    }
    return result;
}

方法二、使用 Map 统计频率并找出前 K 个高频元素

JavaScript Map 详解

Map 是 ES6 引入的一种新的数据结构,它类似于对象(Object),但提供了更强大的键值对存储功能。

1. Map 基本特性

  • 键值对集合:存储键值对,类似 Object
  • 键的类型:可以是任意值(对象、函数、原始值)
  • 顺序保证:Map 会记住键的原始插入顺序
  • 大小可获取:通过 size 属性直接获取元素数量
  • 高性能:在频繁增删键值对的场景下表现优于 Object

2. Map 与 Object 的区别

特性MapObject
键的类型任意值只能是 String 或 Symbol
顺序按插入顺序ES6后也有顺序但不完全可靠
大小size 属性获取需要手动计算
原型无原型链有原型链可能引起键名冲突
默认键有默认属性如 toString
性能频繁增删时更优静态键值对时更优

3. Map 的基本操作

创建 Map

const map = new Map(); // 空Map
const mapWithValues = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]);

添加/更新元素

map.set('name', 'Alice');
map.set(123, 'number key');
map.set({}, 'object key');

获取元素

map.get('name'); // 'Alice'
map.get('nonexistent'); // undefined

检查键是否存在

map.has('name'); // true

删除元素

map.delete('name'); // 返回布尔值表示是否删除成功

清空 Map

map.clear(); // 移除所有键值对

获取大小

map.size; // 返回Map中键值对的数量

4. Map 的迭代方法

forEach 方法

map.forEach((value, key) => {
  console.log(key, value);
});

for...of 循环

for (const [key, value] of map) {
  console.log(key, value);
}

获取键、值、条目

map.keys(); // 返回键的迭代器
map.values(); // 返回值的迭代器
map.entries(); // 返回键值对的迭代器

5. Map 的高级用法

使用对象作为键

const objKey = { id: 1 };
map.set(objKey, 'value associated with object');
console.log(map.get(objKey)); // 'value associated with object'

链式调用

map.set('a', 1)
   .set('b', 2)
   .set('c', 3);

转换数组

// Map转数组
const arr = Array.from(map);
// 或使用展开运算符
const arr2 = [...map];

合并 Map

const first = new Map([[1, 'one'], [2, 'two']]);
const second = new Map([[2, 'two-new'], [3, 'three']]);
const merged = new Map([...first, ...second]);
// 结果: {1 => "one", 2 => "two-new", 3 => "three"}

6. 性能考虑

  • 查找速度:Map 的查找操作接近 O(1) 复杂度
  • 插入速度:Map 的插入操作通常比 Object 快
  • 内存占用:Map 通常比 Object 占用更多内存
  • 大量数据:当键值对数量很大时,Map 的性能优势更明显

7. 使用场景

  1. 需要键不是字符串/符号的情况
  2. 需要维护插入顺序的场景
  3. 频繁增删键值对的场景
  4. 需要知道数据大小的场景
  5. 避免与原型属性冲突的场景

8. 注意事项

  1. 键的比较:Map 使用 "SameValueZero" 算法比较键(类似于 ===,但 NaN 等于 NaN)
  2. 内存泄漏:使用对象作为键时,即使对象被设为 null,Map 仍会保留引用
  3. 序列化:Map 不能直接 JSON.stringify,需要先转换为数组
  4. 浏览器兼容性:所有现代浏览器都支持 Map,但旧版 IE 不支持

9. 实际应用示例

统计词频

function wordFrequency(text) {
  const words = text.split(/\s+/);
  const frequency = new Map();
  
  for (const word of words) {
    const count = frequency.get(word) || 0;
    frequency.set(word, count + 1);
  }
  
  return frequency;
}

缓存实现

class SimpleCache {
  constructor() {
    this.cache = new Map();
  }
  
  set(key, value) {
    this.cache.set(key, { 
      value,
      timestamp: Date.now() 
    });
  }
  
  get(key) {
    const entry = this.cache.get(key);
    return entry ? entry.value : null;
  }
  
  clearExpired(expireTime) {
    const now = Date.now();
    for (const [key, entry] of this.cache) {
      if (now - entry.timestamp > expireTime) {
        this.cache.delete(key);
      }
    }
  }
}

Map 是 JavaScript 中非常强大且灵活的数据结构,特别适合需要复杂键或有序键值对的场景。理解并熟练使用 Map 可以显著提高代码的质量和性能。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function (nums, k) {
    // 1. 使用Map统计频率
    const frequencyMap = new Map();
    for (const num of nums) {
        frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
    }

    // 2. 将Map转换为数组并排序
    const sortedEntries = Array.from(frequencyMap.entries())
        .sort((a, b) => b[1] - a[1]);

    // 3. 提取前k个高频元素
    return sortedEntries.slice(0, k).map(entry => entry[0]);
};

相关文章:

  • 互质的数-蓝桥20245
  • 第二节:React 基础篇-受控组件 vs 非受控组件
  • springboot网站项目+layui框架动态渲染table表格数据信息
  • Apache Doris内存与超时参数配置详解
  • (四)机器学习---逻辑回归及其Python实现
  • cat命令查看文件行数
  • RK3568 基于Gstreamer的多媒体调试记录
  • 2025年工会考试题库及答案
  • 深度学习基础--CNN经典网络之InceptionV1研究与复现(pytorch)
  • 【力扣03】无重复字符的最长子串
  • 4月11日随笔
  • 【深入浅出 Git】:从入门到精通
  • onenote的使用技巧以及不足之处
  • 【网络安全 | 项目开发】Web 安全响应头扫描器(提升网站安全性)
  • 【路由交换方向IE认证】BGP常用属性(除公认必遵外的属性)
  • uniapp离线打包提示未添加videoplayer模块
  • 分布式水文模型丨WRF-Hydro建模与案例应用、从软件安装,到案例实践
  • 【IDEA】创建 SpringBoot 项目连接 MySQL
  • C# net CMS相关开源软件 技术选型 可行性分析
  • 0411 | 软考高项笔记:项目立项
  • 做网站拍幕布照是什么意思/合肥网络关键词排名
  • 网站平台怎么建立的/百度快速收录账号购买
  • 光大成贤建设有限公司网站/上海公司排名
  • 微信做淘宝客网站有哪些/域名关键词排名查询
  • 做网站买空间多少钱/百度关键词排名销售
  • 网站建设网站备案所需资料/苏州seo公司