leetcode刷题(javaScript)——栈相关场景题总结

news2024/9/21 0:40:57

 在LeetCode刷题中,栈是一个非常有用的数据结构,可以解决许多问题,包括但不限于以下几类问题:

  1. 括号匹配问题:例如检查括号序列是否有效、计算表达式的值等。
  2. 逆波兰表达式求值:使用栈来实现逆波兰表达式的计算。
  3. 柱状图中最大矩形:通过维护一个递增栈来解决柱状图中最大矩形的问题。
  4. 简化路径:使用栈来简化给定的文件路径。
  5. 二叉树的遍历:使用栈来实现二叉树的前序、中序、后序遍历等。

栈的主要思想是“后进先出”(Last In First Out,LIFO),即最后进入栈的元素最先出栈。栈通常用于解决需要“回溯”或“撤销”操作的问题,以及需要维护“最近相关性”的问题。

对于新手刷题,以下是一些建议:

  1. 掌握基本操作:首先要熟练掌握栈的基本操作,包括入栈、出栈、获取栈顶元素等。
  2. 理解栈的应用场景:了解栈在解决问题中的常见应用场景,例如上面提到的几类问题。
  3. 练习经典问题:尝试解决一些经典的栈相关问题,例如括号匹配、逆波兰表达式求值等。
  4. 多思考、多实践:在解决问题时,多思考不同的解题思路,多实践不同类型的题目,逐渐提升对栈的理解和应用能力。

20. 有效的括号

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

有效字符串需满足:

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

主要思路是遇到左括号推入栈

遇到右括号,将栈顶左括号移除,并和它进行对比。

不相等return false

否则继续比较,直到最后,判断是否还有没匹配上的左括号

 刚开始刷题,先看第一个版本 ,有两个地方可以优化,一个是很明显的stask.length的判断可以改写为三元表达式。另外可以用对象key:value的特性进行括号对比。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    const stask = [];
    for (let item of s) {
        if (['(', '[', '{'].includes(item)) {
            stask.push(item);
        } else {
            const pre = stask.pop();
            if ((pre === '(' && item === ')') || (pre === '[' && item === ']') || (pre === '{' && item === '}')) {
                continue;
            } else {
                return false;
            }
        }
    }
    if (stask.length > 0) {
        return false;
    } else {
        return true;
    }
};

优化后的代码

使用了一个映射表 map 来存储左右括号的对应关系,这样可以使代码更加清晰易懂。同时,在判断右括号是否匹配时,直接与栈顶元素进行比较,避免了多次判断的情况,提高了效率。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    const stask = [];
    const map = {
        '(': ')',
        '[': ']',
        '{': '}',
    };
    for (let char of s) {
        if (char in map) {
            stask.push(char);
        } else if (char != map[stask.pop()]) {
            return false;
        }
    }
    return stask.length === 0;
};

这里有个巧妙之处,可以用in判断一个属性是不是对象的属性!!

此外一些小点,如可用用for of进行字符串的遍历

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

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

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

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

 思路是使用栈来消除相邻重复的字符。当前遍历的元素和前一个栈顶元素比较,如果不等,将已经pop出的元素和当前元素入栈,继续。否则,相等,当重复的元素已经pop了,无需处理。

比较后留在栈的元素就是没有相邻重复的数组,再将数组转成字符串。使用数组的join方法。

这里相同的是对字符串操作时,使用for of进行遍历即可

/**
 * @param {string} s
 * @return {string}
 */
var removeDuplicates = function (s) {
    let stack = [];
    for (let char of s) {
        const pre = stack.pop();
        if ( char != pre) {
            stack.push(pre);
            stack.push(char);
        }
    }
    return stack.join('');
};

 1614. 括号的最大嵌套深度

 这道题原文很长,其实主要就是求括号最大嵌套层数,可以不考虑中间出现的数字和运算符。

 思想是用栈存储左括号,当遇到右括号的时候先看栈内左括号的个数,即当前的嵌套层数,将其保存下来。然后将栈顶左括号出栈;只要统计栈的最大长度即可得到最大嵌套深度

/**
 * @param {string} s
 * @return {number}
 */
var maxDepth = function (s) {
    let stack = [];
    let tempDep = 0;
    let dep = 0;
    for (let char of s) {
        //char是左括号
        if (char === '(') {
            stack.push(char);
        } else if (char === ')') {
            //char是右括号
            tempDep = stack.length;
            dep = tempDep > dep ? tempDep : dep;
            stack.pop();
        }
    }
    return dep;
};

 这样只用考虑左括号和右括号就好啦

 1544. 整理字符串

给你一个由大小写英文字母组成的字符串 s 。一个整理好的字符串中,两个相邻字符 s[i]s[i+1],其中 0<= i <= s.length-2 ,要满足如下条件:

  • s[i] 是小写字符,则 s[i+1] 不可以是相同的大写字符。
  • s[i] 是大写字符,则 s[i+1] 不可以是相同的小写字符。

请你将字符串整理好,每次你都可以从字符串中选出满足上述条件的 两个相邻 字符并删除,直到字符串整理好为止。

请返回整理好的 字符串 。题目保证在给出的约束条件下,测试样例对应的答案是唯一的。

注意:空字符串也属于整理好的字符串,尽管其中没有任何字符。

 思路:用栈保留不相邻大小写字符,每次用栈顶元素与当前元素比较,如果两个是相邻的大小写字符,则弹出栈顶元素。否则,当前元素入栈。

使用字符串的charCodeAt方法获取字符串的ASCII码;使用Math.abs返回相差的绝对值,大小写相差32

返回字符串,需要将stack数组转字符串,同样也用数组的join方法

/**
 * @param {string} s
 * @return {string}
 */

var makeGood = function (s) {
    let stack = [];
    for (let char of s) {
        let stackTop = stack[stack.length - 1];
        if (stackTop && Math.abs(char.charCodeAt(0) - stackTop.charCodeAt(0)) == 32) {
            stack.pop();
        } else {
            stack.push(char);
        }
    }
    return stack.join("");
};

682. 棒球比赛 ——逆波兰表达式求值

你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。

比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:

  1. 整数 x - 表示本回合新获得分数 x
  2. "+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
  3. "D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
  4. "C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。

请你返回记录中所有得分的总和。

 这是一道用栈来进行求值的,当前项与前一项或前两项有关系,且已经存在的元素可以被删除。就可以考虑栈对栈顶元素的操作

/**
 * @param {string[]} operations
 * @return {number}
 */
const calPoints = function (operations) {
  const stack = [];
  for (let item of operations) {
    if (item === "C") {
      stack.pop();
    } else if (item === "D") {
      stack.push(stack[stack.length - 1] * 2);
    } else if (item === "+") {
      stack.push(stack[stack.length - 1] + stack[stack.length - 2]);
    } else {
      stack.push(Number(item));
    }
  }

  return stack.reduce((total, num) => total + num, 0);
};

 思想是用栈存储确定后的没项元素,根据当前匹配的是数字还是符号进行响应规则增加或删除项。最后用数组的reduce方法对数字求和。比较简单

 150. 逆波兰表达式求值

逆波兰表达式是一种不需要括号来标识优先级的数学表达式表示方法,它通过将操作符放在操作数的后面来表示运算顺序。

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

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

注意:

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

 

 思路:用栈存储操作数,遇到操作符号+-*/时从栈顶依次弹出两个数,分别作为右操作数和左操作数。需要手动处理+-*/匹配时的运算。并且考虑除法向0靠拢。js的Math函数,trunc提供了这个方法。这个平时真用的挺少的。先给个初版的,后面有个进阶优化的代码

/**
 * @param {string[]} tokens
 * @return {number}
 */
var evalRPN = function (tokens) {
    let stack = [];
    const symbol = ['+', '-', '*', '/'];
    for (let item of tokens) {
        if (symbol.includes(item)) {
            let right = stack.pop();
            let left = stack.pop();
            stack.push(getData(left, right, item));
        } else {
            stack.push(Number(item));
        }
    }
    return stack[0];
};
function getData(left, right, operate) {
    switch (operate) {
        case '+':
            return left + right;
        case '-':
            return left - right;
        case '*':
            return left * right;
        case '/':
            return Math.trunc(left / right);
    }

}

 这里比较操作符用数组includes方法减少了比较,那如何更加匹配时的简化运算逻辑呢

考虑使用ES6提供的Map对象,key值存放符合,value值存放一个函数,接收两个操作数。

通过Map对象的has方法判断key值是否存在,通过get方法获取函数。parseInt和Number都可以转换数字,这里只是提供了一种方法

var evalRPN = function (tokens) {
    const stack = [];
    const operators = new Map([
        ['+', (a, b) => a + b],
        ['-', (a, b) => a - b],
        ['*', (a, b) => a * b],
        ['/', (a, b) => Math.trunc(a / b)]
    ]);

    for (let token of tokens) {
        if (operators.has(token)) {
            let right = stack.pop();
            let left = stack.pop();
            let result = operators.get(token)(left, right);
            stack.push(result);
        } else {
            stack.push(parseInt(token));
        }
    }

    return stack[0];
};

 

844. 比较含退格的字符串

给定 st 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true# 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var backspaceCompare = function (s, t) {
    return getStr(s) === getStr(t);
};

function getStr(str) {
    let stack = [];
    for (let char of str) {
        if (char != '#') {
            stack.push(char);
        } else {
            stack.pop();
        }
    }
    return stack.join('');
}

 思路:用栈保存s或t最终的结果,在比较两者是否相等。

不等于#时push进,为#时将栈顶元素弹出。

比较简单

 2696. 删除子串后的字符串最小长度

给你一个仅由 大写 英文字符组成的字符串 s 。你可以对此字符串执行一些操作,在每一步操作中,你可以从 s 中删除 任一个 "AB""CD" 子字符串。通过执行操作,删除所有 "AB""CD" 子串,返回可获得的最终字符串的 最小 可能长度。注意,删除子串后,重新连接出的字符串可能会产生新的 "AB""CD" 子串。

/**
 * @param {string} s
 * @return {number}
 */
var minLength = function (s) {
    const map = {
        'A': 'B',
        'C': 'D'
    }
    let stack = [];
    for (let char of s) {
        if (stack.length > 0 && char === map[stack[stack.length - 1]]) {
            stack.pop();
        } else {
            stack.push(char);
        }
    }
    return stack.length;
};

也是一道典型的字符匹配题目,用栈加map存储键值对比较真的无敌了。

不啰嗦了,这里返回的是数组长度注意一下就行

 71. 简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。 对于此问题,任何其他格式的点(例如,'...')均被视为文件/目录名称。

请注意,返回的 规范路径 必须遵循下述格式:

  • 始终以斜杠 '/' 开头。
  • 两个目录名之间必须只有一个斜杠 '/'
  • 最后一个目录名(如果存在)不能 '/' 结尾。
  • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.''..')。

返回简化后得到的 规范路径

 

 终结题目的要求:/不能重复;..表示回退一步;.表示在当前;这道题的难点是对/的处理。要是按/进行分隔呢,只保留文件名和操作运算符。看下代码怎么实现吧

/**
 * @param {string} path
 * @return {string}
 */
var simplifyPath = function (path) {
    let stack = [];
    const pathArr = path.split('/');
    pathArr.forEach(item => {
        if (item === '..') {
            stack.pop();
        } else if (item && item != '.') {
            stack.push(item);
        }
    })
    return stack.length ? '/' + stack.join('/') : '/';
};

 思路:按照/将字符分开为数组。然后用栈存储当前经过的路径上的文件夹名称。

遇到..栈顶弹出,遇到.不处理。遇到文件名,push进栈

最后将文件名按/拼接,注意以/开头

144. 二叉树的前序遍历

(1)先(根)序遍历(根左右)

不管是前、中、后序遍历,总的来说都要从根元素开始,把左子树遍历一遍在把右子树遍历一遍,只不过过程中打印节点顺序不同而有所差异。栈的思想是当前元素遍历到了,但是它的左子树或右子树没遍历完,需要入栈,当左子树或右子数遍历了即可出栈

/**
 * 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) {
    let arr = [];
    let stack = [];
    let current = root;
    while (current != null || stack.length > 0) {
        while (current!= null) {
            arr.push(current.val);
            stack.push(current);
            current = current.left;

        }
        current = stack.pop();
        current = current.right;
    }
    return arr;
};

 arr存放遍历后的数据,优先入栈跟节点,其次左节点,最后右节点。每次循环仅得到一个节点,切记不多写,容易混乱

94. 二叉树的中序遍历

中(根)序遍历(左根右)

/**
 * 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 inorderTraversal = function (root) {
    const arr = [];
    const stack = [];
    let current = root;
    while (current != null || stack.length > 0) {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        arr.push(current.val);
        current = current.right;

    }
    return arr;
};

 入栈的顺序是一样的,只不过arr添加的顺序不同,这里先push的一定是左子树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1480479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux线程【1概念】

目录 前言&#xff1a; 正文&#xff1a; 1.什么是线程&#xff1f; 1.1基本概念 1.2线程理解 1.3进程与线程的关系 1.4线程使用 2.重谈地址空间 2.1页表的大小 2.2、内存与磁盘的交互 2.3深入页表 2.4小结 3.线程概念总结 3.1、再谈线程 3.2线程的优点 3.3…

MYSQL 解释器小记

解释器的结果通常通过上述表格展示&#xff1a; 1. select_type 表示查询的类型 simple: 表示简单的选择查询&#xff0c;没有子查询或连接操作 primary:表示主查询&#xff0c;通常是最外层的查询 subquery :表示子查询&#xff0c;在主查询中嵌套的查询 derived: 表示派…

pip降级在pycharm中

PyCharm依赖于"–build-dir"参数安装第三方库&#xff0c;但该参数在最新的23.0版pip中已删除 解决办法就是降级pip&#xff0c;PyCharm中选择File&#xff0c;找到编译器&#xff0c;点击pip&#xff0c;勾选对应版本即可 或者在cmd中执行运行python -m pip install…

android开发软件下载,2024年Android开发陷入饱和

谈起性能优化&#xff0c;我想问&#xff0c; 什么是性能优化&#xff1f;你的理解呢&#xff1f; **首先什么是性能&#xff1a;**在同一个手机里面&#xff0c;同样功能的app&#xff0c;哪个跑的快&#xff0c;哪个不卡&#xff0c;哪个就性能高。我们这篇文章就是解决那些…

SQL注入漏洞解析--less-6

1.第六关了。 2.这个和第五关有点像&#xff0c;只是换成了双引号&#xff0c;接下来的都一样&#xff0c;看我操作(换个函数试一下extractvalue&#xff0c;他的报错位置在第二个&#xff0c;那我就利用一下) 3.爆库名 ?id1"%20and%20extractvalue(1,concat(0x7e,(sele…

后地产时代,卫浴品牌有何变革之道?

联系与卫浴密切相关的房地产行业现状来看&#xff0c;卫浴行业正处于变革期。 国家统计局数据显示&#xff0c;2023年&#xff0c;商品房销售面积111735万平方米&#xff0c;比上年下降8.5%&#xff0c;其中住宅销售面积下降8.2%&#xff1b;商品房销售额116622亿元&#xff0…

低功耗微处理器复位检测电路,工作温度范围-40~+80℃,可与μP监控产品兼容——D706

近年来&#xff0c;微处理器在IT业控制领域和智能化产品中得到了广泛的应用。在系统和产品的开发设计过程中&#xff0c;为了提高其抗干扰能力&#xff0c;使用μP监控是首选技术措施之一。监控芯片可为系统提供上电、掉电复位功能&#xff0c;也可提供其它功能&#xff0c;如后…

二分通用模板+例题java

&#x1f9e1;&#x1f9e1;二分大前提&#x1f9e1;&#x1f9e1; 有序数列&#xff01;&#xff01; &#x1f9e1;&#x1f9e1;模板&#x1f9e1;&#x1f9e1; 查找具体某个元素 while(left<right) &#xff1a;三个分支&#xff0c;在循环里直接返回答案 //二分查找具…

JSON 文件里的 “$schema” 是干什么用的?

最近我在做一些前端项目&#xff0c;我发现有的配置文件&#xff0c;比如 .prettierrc.json 或者 tsconfig.json 里面都会看到一个 $schema 字段&#xff0c;有点好奇&#xff0c;就查了一下。 什么是 JSON Schema JSON Schema是一种基于JSON (JavaScript Object Notation) 的…

电梯物联网之梯控相机方案-防止电瓶车进电梯

梯控现状 随着电梯产品在智能化建筑的日益普及,对于电梯的智能化管理 安全性需求 的要求越来越迫切。尤其今年来随着电瓶车的大量普及&#xff0c;发起多起楼道、轿厢电瓶车着火恶性事件&#xff0c; 造成了极大的社会 负面影响。控制电瓶车进入单元门&#xff0c;楼道以及电梯…

浅析智能照明控制系统在图书馆照明节能中的应用

彭姝麟 Acrelpsl 0 引 言 照明耗电在各国总发电量中都占有很大比例&#xff0c;根据2004年国家住房和城乡建设部的统计&#xff0c; 我国照明耗电约占全国发电总量的10&#xff05;一12&#xff05;。 对一些照明时间较长、照明场所较多的机构&#xff0c;如高等学校&#xf…

百度新版VMP

百度搞事情哇最近&#xff0c;新上验证类型&#xff0c;曲线匹配&#xff01;&#xff01;&#xff01; 这年头儿&#xff0c;连百度都还是上vmp了兄弟们&#xff0c;越来越难咯。 个人感觉 想学好vm的话&#xff0c;首先得找个好位置插装&#xff0c;然后慢慢分析。 轨迹信息…

Spring注解之json 数据处理

目录 1. 过滤 json 数据 2. 格式化 json 数据 3. 扁平化对象 1. 过滤 json 数据 JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。 //生成json时将userRoles属性过滤 JsonIgnoreProperties({"userRoles"}) public class User { ​private S…

C++的继承和多态

继承和多态 继承继承的权限继承的子父类访问派生类的默认成员函数菱形继承&#xff08;C独有&#xff09;【了解】虚拟继承什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;什么是菱形虚拟继承&#xff1f;如何解决数据冗余和二义性的继承和组合的区别&#xff1f;…

Redis冲冲冲——事务支持,AOF和RDB持久化

目录 引出Redis事务支持&#xff0c;AOF和RDB持久化1、Redis的事务支持2、Redis的持久化 Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Redis冲冲冲——事务支持&#xff0c;AOF和RDB持久化 Redis事务支持&#xff0c;AOF和…

Windows系统x86机器安装龙芯(loongarch64)3A5000虚拟机系统详细教程

本次介绍在window系统x86机器上安装loongarch64系统的详细教程。 1.安装环境准备。 首先&#xff0c;你得有台电脑。 配置别太差&#xff0c;至少4核8G内存&#xff0c;安装window10或者11都行&#xff08;为啥不能是Window7&#xff0c;你要用也不是不行&#xff0c;你先解决…

模拟算法题练习(二)(DNA序列修正、无尽的石头)

&#xff08;一、DNA序列修正&#xff09; 问题描述 在生物学中&#xff0c;DNA序列的相似性常被用来研究物种间的亲缘关系。现在我们有两条 DNA序列&#xff0c;每条序列由 A、C、G、T 四种字符组成&#xff0c;长度相同。但是现在我们记录的 DNA序列存在错误&#xff0c;为了…

解析电源模块测试条件与测试步骤 快速完成测试

高温高湿储存测试是电源模块环境适应性测试内容之一&#xff0c;在实际使用过程中由于应用场景不同电源所处的环境也是多样的&#xff0c;因此需要测试电源对各种环境的适应能力&#xff0c;提高电源的性能和可靠性。 电源高温高湿存储测试的目的是为了测量环境对电源结构、元件…

Spring - InitializingBean、@PostConstruct、@Bean(initMethod = “init“)和构造方法执行优先级比较

执行顺序优先级 构造方法 > postConstruct > afterPropertiesSet > init方法 代码案例 Component public class InitializingBeanTest implements InitializingBean {public InitializingBeanTest(){System.out.println("构造方法");}Overridepublic void…

现代企业架构框架——技术架构

现代企业架构框架——技术架构。 技术架构是对某一技术问题(需求)解决方案的结构化描述,由构成解决方案的组件结构及之间的交互关系构成。广义上的技术架构是一系列涵盖多类技术问题设计方案的统称,例如部署方案、存储方案、缓存方案、日志方案等等。 现代企业架构框架(M…