数据结构与算法--栈一

438 阅读8分钟

文章简要概述

  • 本文主要进行栈相关的算法题刷题题解记录,记录栈相关算法以及如何解。
  • 这文一共有6道题,主要介绍leetcode中棒球比赛比较含退格的字符串验证栈序列有效的括号删除最外层的括号移除无效的括号的解题思路。

栈相关概念

栈(stack)可以理解成一个收纳盒,放在最底层,最后才能拿出来,在 LIFO 数据结构中,将首先处理添加到队列中的最新元素。

与队列不同,栈是一个 LIFO 数据结构。通常,插入操作在栈中被称作入栈 push 。与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,退栈 pop ,将始终删除队列中相对于它的最后一个元素。关于出栈入栈演示看下图。(图片资源来源leetcode)

691e2a8cca120acb18e77379c7cd7eec3835c8c102d1c699303f50accd1e09df-出入栈 (1).gif

与栈相关算法

棒球比赛

棒球比赛--leetcode

题目大意:

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

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

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

示例

输入:ops = ["5","2","C","D","+"]

输出:30

解释:

"5" - 记录加 5 ,记录现在是 [5]

"2" - 记录加 2 ,记录现在是 [5, 2]

"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5]

"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10]

"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15]

所有得分的总和 5 + 10 + 15 = 30

解题思路:

  • 是一个典型的栈问题,创建一个栈,这里用数组模拟。
  • 遍历数组,判断当前操作符是数,放入到栈中(push),如果是C,就弹出栈中元素;如果是D取出栈顶元素 * 2,如果是+,从栈中取出两位元素,进行求和,并将和值放入到栈中。
  • 遍历栈中元素,求的和值。

代码:

/**
 * @param {string[]} ops
 * @return {number}
 */
function calPoints (ops) {
   const stack = [];
   for (let opt of ops) {
      if (opt === 'C') {
        stack.pop();
      } else if (opt === 'D') {
        const val = stack.pop();
        stack.push(val, val * 2);
      } else if (opt === '+') {
        const val1 = stack.pop();
        const val2 = stack.pop();
        stack.push(val2, val1, val1 + val2);
      } else {
          stack.push(Number(opt));
      }
   }
   return stack.reduce((total, val) => total + val, 0);
};

比较含退格的字符串

比较含退格的字符串--leetcode

题目大意:

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。

如果相等,返回 true ;否则,返回 false 。

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

示例:

输入: s = "ab#c", t = "ad#c"

输出: true

解释: S 和 T 都会变成 “ac”。

解题思路:

  • 题目中说到#出现会删除前一项值,很符合栈的结构特性。
  • 定义一个栈,将字符串遍历,把每一个值放入到栈中,遇到#从栈顶取出元素。
  • 返回栈中剩余的元素,比较输入的两个字符,处理后的值是否相等。

代码:

/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
 function getStr (str) {
     const stack = [];
    for(let k in str) {
        if (str[k] === '#') {
          stack.pop();
        } else {
            stack.push(str[k]);
        }
    }
    return stack.join('');
 }
var backspaceCompare = function(s, t) {
  const s1 = getStr(s);
  const t1 = getStr(t);
  return s1 === t1
};

验证栈序列

验证栈序列--leetcode

题目大意:

给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。

示例:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]

输出:true

解释:我们可以按以下顺序执行:

push(1), push(2), push(3), push(4), pop() -> 4,

push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

解题思路:

  • 这道题很容易让人读完题,明白要做什么,但编码的时候不太容易想清楚怎么实现。
  • 我们可以发现一个规律,pushed序列中的元素,我们按照顺序放入栈中,当放入的元素与popped序列中的第一个元素相等时,我们要将pushed序列中最后放入的这个元素从栈中弹出。
  • 再比较popped序列下一位元素与pushed序列中最后一位元素是否相等,相等接着弹出元素,直到不相等或栈元素为空。

代码:

/**
 * @param {number[]} pushed
 * @param {number[]} popped
 * @return {boolean}
 */
function validateStackSequences (pushed, popped) {
  const stack = [];
  let j = 0;
  for(let i = 0; i < popped.length; i++) {
      stack.push(pushed[i]);
      while(stack.length && stack[stack.length - 1] === popped[j]) {
          stack.pop();
          j++;
      }
  }
  return !stack.length;
};

有效的括号

有效的括号--leetcode

题目大意:

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例:

案例一:

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

输出: true


案例二:

输入: s = "(]"

输出: false

解题思路:

  • 这道题如果读完题能想到用栈来处理,问题就解决一半了。
  • 括号是相对应的。如下图形式,将元素入栈,如果出现右括号时判断栈是否为空,为空肯定不符合条件,或者栈顶元素是否是左括号,不是也不符合条件,弹出元素。
  • 最后判断栈是否为空即可。

baa8829ac398e665eb645dca29eadd631e2b337e05022aa5a678e091471a4913-20.gif

代码:

/**
 * @param {string} s
 * @return {boolean}
 */
function isValid (s) {
   const l = s.length;
   if (l % 2 === 1) return false;
   const stack = [];
   const symbol = new Map([
       [')', '('],
       ['}', '{'],
       [']', '['],
    ]);
   for(let val of s) {
       if (symbol.has(val)) {
           if (!stack.length || stack[stack.length - 1] !== symbol.get(val)) return false;
           stack.pop();
       } else {
           stack.push(val);
       }
   }
   return !stack.length
};

删除最外层的括号

删除最外层的括号--leetcode

题目大意:

有效括号字符串为空 ""、"(" + A + ")" 或 A + B ,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。

例如,"","()","(())()" 和 "(()(()))" 都是有效的括号字符串。
如果有效字符串 s 非空,且不存在将其拆分为 s = A + B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。

给出一个非空有效字符串 s,考虑将其进行原语化分解,使得:s = P_1 + P_2 + ... + P_k,其中 P_i 是有效括号字符串原语。

对 s 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 s 。

示例:

输入:s = "(()())(())" 输出:"()()()" 解释: 输入字符串为 "(()())(())",原语化分解得到 "(()())" + "(())", 删除每个部分中的最外层括号后得到 "()()" + "()" = "()()()"。

解题思路:

  • 需要去除外层括号,为了找到这些需要去除的外层括号,可以使用到计数器
  • 遇到左括号,我们的计数器 +1,遇到右括号,我们的计数器 -1
  • 可以得到一下规律,可以结合下图理解
    1. 遇到左括号,当前计数值大于 00 ,则属于有效的左括号。
    2. 遇到右括号,当前计数值大于 11 ,则属于有效的右括号。

企业微信截图_f3ffce0b-69b5-4e5c-94d5-39487e0aede9.png 图一

企业微信截图_538c1af6-458a-4212-84c5-a143805df187.png 图二

企业微信截图_8ba8c089-102f-4d76-bf13-ad31eab63197.png 图三

代码:

/**
 * @param {string} s
 * @return {string}
 */
var removeOuterParentheses = function(s) {
  let count = 0;
  let res = '';
  for(let val of s) {
      if (val === '(' && count++ > 0) res += val;
      if (val === ')' && count-- > 1) res += val;
  }
  return res;
};

移除无效的括号

移除无效的括号--leetcode

题目大意:

给你一个由 '('、')' 和小写字母组成的字符串 s。

你需要从字符串中删除最少数目的 '(' 或者 ')' (可以删除任意位置的括号),使得剩下的「括号字符串」有效。

请返回任意一个合法字符串。

有效「括号字符串」应当符合以下 任意一条 要求:

空字符串或只包含小写字母的字符串
可以被写作 AB(A 连接 B)的字符串,其中 A 和 B 都是有效「括号字符串」
可以被写作 (A) 的字符串,其中 A 是一个有效的「括号字符串」

示例:

输入:s = "lee(t(c)o)de)"

输出:"lee(t(c)o)de"

解释:"lee(t(co)de)" , "lee(t(c)ode)" 也是一个可行答案。

解题思路:

  • 这道题可以先除去干扰项,本质是看括号,lee(t(c)o)de)可以看成(())),右括号多一个
  • 那么只要遍历字符串,标记多余括号的位置,在去除标记为的值,即可得到。
  • 遍历到左括号时,放入标记,匹配到右括号时,查看栈中是否有左括号,存在就取消之前标记。

代码:

/**
 * @param {string} s
 * @return {string}
 */
function minRemoveToMakeValid (s) {
   let leftDel = [];
   let rightDel = [];
   for(let i in s) {
       if (s[i] === '(') {
           leftDel.push(i);
       } else if (s[i] === ')') {
           if (leftDel.length) {
               leftDel.pop();
           } else {
               rightDel.push(i);
           }
       }
   }
   const del = [...leftDel, ...rightDel];
   const res = [...s];
   for(let i = 0; i < del.length; i++) {
      res[del[i]] = '';
   }
   return res.join('');
};

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是栈部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

数据结构与算法-队列一

数据结构与算法-队列二

数据结构与算法-链表一

数据结构与算法-链表二

数据结构与算法-链表三

有兴趣的可以一起来刷题,如果文章对你有帮助,感谢点赞👍,关注!