前端面试常见堆栈算法题之字符串处理

154 阅读6分钟

[中等] 字符串解码 - Leecode 394

题目

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例

示例 1:
输入: s = "3[a]2[bc]"
输出: "aaabcbc"

示例 2:
输入: s = "3[a2[c]]"
输出: "accaccacc"

示例 3:
输入: s = "2[abc]3[cd]ef"
输出: "abcabccdcdcdef"

示例 4:
输入: s = "abc3[cd]xyz"
输出: "abccdcdcdxyz"

提示:

  • 1 <= s.length <= 30
  • s 由小写英文字母、数字和方括号 '[]' 组成
  • s 保证是一个 有效 的输入。
  • s 中所有整数的取值范围为 [1, 300]

解法

var decodeString = function(s) {
    const stack = [];
    let num = '';  // 用于存储数字
    for (let i = 0; i < s.length; i++) {
        const char = s[i];
        
        if (/\d/.test(char)) {
            // 如果是数字,就继续累加到 num 中
            num += char;
        } else if (char === '[') {
            // 如果是 '[', 把当前的数字压入栈中
            if (num) {
                stack.push(Number(num));
                num = ''; // 重置 num,准备下一个数字
            }
            // 压入 '['
            stack.push(char);
        } else if (char === ']') {
            // 如果是 ']', 处理解码
            let temp = '';
            // 弹出栈中的元素,直到遇到 '['
            while (stack[stack.length - 1] !== '[') {
                temp = stack.pop() + temp;
            }
            stack.pop(); // 弹出 '['
            let count = stack.pop(); // 获取重复次数
            // 将重复的子串压入栈中
            stack.push(temp.repeat(count));
        } else {
            // 如果是字母,直接压入栈中
            stack.push(char);
        }
    }
    // 最后将栈中的内容连接成最终的字符串返回
    return stack.join('');
};

[中等] 基本计算器II - Leecode 227

题目

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。

注意: 不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

示例

示例 1:
输入: s = "3+2*2"
输出: 7

示例 2:
输入: s = " 3/2 "
输出: 1

示例 3:
输入: s = " 3+5 / 2 "
输出: 5

提示:

  • 1 <= s.length <= 3 * 105
  • s 由整数和算符 ('+', '-', '*', '/') 组成,中间由一些空格隔开
  • s 表示一个 有效表达式
  • 表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
  • 题目数据保证答案是一个 32-bit 整数

解法

/**
 * @param {string} s - 输入的数学表达式字符串
 * @return {number} - 返回计算结果
 */
var calculate = function(s) {
    const stack = []; // 用于存储中间计算结果的栈
    let preOperator = '+'; // 上一个运算符,初始化为 '+'
    let num = ''; // 当前数字的字符串形式

    // 遍历字符串
    for (let i = 0; i < s.length; i++) {
        const char = s[i]; // 当前字符

        // 如果是数字字符,则将其追加到当前数字字符串
        if (!isNaN(char)) {
            num += char;
        }

        // 如果遇到非数字字符或已经到字符串的最后一个字符
        if (isNaN(char) || i === s.length - 1) {
            switch (preOperator) { // 根据上一个运算符执行操作
                case '+':
                    stack.push(+num); // 将当前数字压入栈
                    break;
                case '-':
                    stack.push(-num); // 将当前数字的负值压入栈
                    break;
                case '*':
                    stack.push(stack.pop() * num); // 弹出栈顶数字,与当前数字相乘后压入栈
                    break;
                case '/':
                    // 弹出栈顶数字,与当前数字相除(向零取整)后压入栈
                    // Math.trunc(n) 返回数字的整数部分(向零取整),如果数字为负,则结果也为负数
                    stack.push(Math.trunc(stack.pop() / num)); 
                    break;
            }

            // 更新上一个运算符为当前字符
            preOperator = char;
            // 重置当前数字
            num = '';
        }
    }

    // 将栈中的所有数字相加,得到最终结果
    return stack.reduce((acc, cur) => acc + cur, 0);
};

[困难] 高级计算器

题目

编写一个函数 StringCalculate(str),接受一个字符串参数 str,并对其中的数学表达式进行计算。双星号(**)表示幂运算。

例如:

  • 如果输入是 "(2+(3-1)*3)**3",输出应该是 512
  • 再比如,如果输入是 "(2-0)(6/2)",输出应该是 6

字符串中可能包含括号,所以必须正确按照算术规则对其进行评估。字符串将包含以下运算符:+-/*()**

如果字符串像这样:#/#*#+#+(#)/#,那么应按照从左到右的顺序进行计算。例如:

  • 对于 #/#*#,先除法再乘法。
  • 对于 +#+(#)/#,先乘法,再除法,最后加法。

所有计算都保证不会产生小数结果,因此不需要考虑四舍五入的问题。

示例

示例 1:
输入: "6*(4/2)+3*1"
输出: 15

示例 2:
输入: "100*2**4"
输出: 1600

解法

抛砖引玉,我只是写了一种解法,也不一定完全正确。如果有更好的解法欢迎分享!

function stringCalculate(str) {
  // 预处理字符串:
  // 1. 处理数字后面直接跟左括号的情况:例如 6(4) → 6*(4)
  // 2. 处理右括号后面直接跟左括号的情况:例如 (2)(3) → (2)*(3)
  str = str.replace(/(\d+|\))\(/g, "$1*(");

  // 辅助函数:计算乘除法,返回处理后的数组
  function handleMulDiv(tokens) {
    let stack = [];
    let i = 0;

    while (i < tokens.length) {
      let token = tokens[i];

      if (token === "*" || token === "/") {
        let prev = stack.pop();
        let next = tokens[i + 1];
        if (token === "*") {
          stack.push(prev * next);
        } else {
          stack.push(prev / next);
        }
        i++; // 跳过下一个操作数
      } else {
        stack.push(token);
      }
      i++;
    }
    return stack;
  }

  // 辅助函数:计算加减法,返回最终结果
  function handleAddSub(tokens) {
    let result = tokens[0];
    for (let i = 1; i < tokens.length; i += 2) {
      let operator = tokens[i];
      let operand = tokens[i + 1];
      if (operator === "+") {
        result += operand;
      } else if (operator === "-") {
        result -= operand;
      }
    }
    return result;
  }

  // 辅助函数:计算指数运算,返回处理后的数组
  function handleExp(tokens) {
    let stack = [];
    let i = 0;

    while (i < tokens.length) {
      let token = tokens[i];

      if (token === "**") {
        let prev = stack.pop();
        let next = tokens[i + 1];
        stack.push(Math.pow(prev, next)); // 计算指数
        i++; // 跳过下一个操作数
      } else {
        stack.push(token);
      }
      i++;
    }
    return stack;
  }

  // 辅助函数:递归地处理括号
  function evaluate(expression) {
    // 处理括号
    let regex = /\(([^()]+)\)/;
    while (regex.test(expression)) {
      expression = expression.replace(regex, (match, subExpr) =>
        evaluate(subExpr)
      );
    }

    // 改进的token分割逻辑
    let tokens = [];
    let number = "";
    let prevChar = "";

    for (let i = 0; i < expression.length; i++) {
      let char = expression[i];

      // 处理数字和小数点
      if (/[\d.]/.test(char)) {
        number += char;
      }
      // 处理负号
      else if (char === "-" && (i === 0 || /[\+\-\*\/\(]/.test(prevChar))) {
        number = "-" + number;
      }
      // 处理运算符
      else {
        if (number !== "") {
          tokens.push(Number(number));
          number = "";
        }
        if (char !== " ") {
          // 处理 ** 运算符
          if (char === "*" && expression[i + 1] === "*") {
            tokens.push("**");
            i++;
          } else {
            tokens.push(char);
          }
        }
      }
      prevChar = char;
    }
    if (number !== "") {
      tokens.push(Number(number));
    }

    // 处理指数运算符 (最优先)
    tokens = handleExp(tokens);

    // 处理乘除法
    tokens = handleMulDiv(tokens);

    // 处理加减法
    return handleAddSub(tokens);
  }

  return evaluate(str);
}

// 测试用例
console.log(stringCalculate("(2+(3-1)*3)**3")); // 输出: 512
console.log(stringCalculate("(2-0)*(6/2)")); // 输出: 6
console.log(stringCalculate("(2-0)(6/2)")); // 输出: 6
console.log(stringCalculate("6(4/2)(6/2)+3*1")); // 输出: 39
console.log(stringCalculate("6*(4/2)+3*1")); // 输出: 15
console.log(stringCalculate("100*2**4")); // 输出: 1600