LeetCode - 224. Basic Calculator
在算法学习的漫漫长路中,我们总会遇到一些颇具挑战性的问题,而 LeetCode 上的第 224 题 —— 基本计算器(Basic Calculator)便是其中之一。这道题要求我们根据给定的算术表达式字符串,计算出其结果,字符串中包含 ‘+’、‘-’、’ ‘、’(‘、’)’ 这些元素。虽然题目看似简单,但其背后涉及的字符串解析与计算逻辑,足以考验我们对算法和编程技巧的掌握程度。今天,就让我们一同深入剖析这道题,并通过 JavaScript 代码来实现它的解法。
1. 问题深度剖析
1.1 输入与输出的明确界定
我们的输入是一个算术表达式字符串。这个字符串可能是简单的 “1 + 1”,也可能是复杂的 “(1 + (4 + 5 + 2) - 3) + (6 + 8)”。无论其形式如何,我们都需要从这个字符串中提取有效的信息,并按照数学运算规则进行计算。而输出,则是这个算术表达式计算后的结果,对于前面提到的两个例子,输出分别为 2 和 23。
1.2 关键挑战点解析
这道题的核心挑战在于如何准确解析字符串,处理其中的空格、运算符、数字以及括号。其中,括号的存在极大地增加了题目的复杂性,因为它改变了运算的优先级,我们需要优先计算括号内的表达式。
2. 解题思路的逐步拆解
2.1 关键要点提炼
解决这个问题的关键在于两个方面:一是精确解析字符串,将其转化为计算机能够理解和处理的数据结构;二是设计合理的算法来计算结果,特别是要处理好括号对运算顺序的影响。
2.2 具体解题步骤
-
a. 解析字符串:我们首先要将输入的字符串解析成一个便于处理的数据结构。在我们的 JavaScript 代码中,通过
pareToArray
函数来完成这一任务。这个函数会遍历输入字符串,将数字、运算符和括号分别提取出来,并处理可能存在的前导符号。例如,如果字符串以 ‘-’ 或 ‘+’ 开头,函数会在前面补 0,以便后续统一处理。在遍历过程中,若遇到数字字符,会判断其前一个元素是否也是数字字符,如果是,则将当前数字字符追加到前一个数字字符串上,从而正确处理多位数的情况。 -
b. 计算括号内结果:在解析完字符串后,我们使用栈来处理表达式。当遇到 ‘)’ 时,我们知道需要开始计算括号内的结果了。此时,我们会从栈中取出相关元素,进行计算,直到遇到对应的 ‘(’。在 JavaScript 代码中,这一过程由
calculateWithoutBracket
函数实现。该函数从栈中弹出元素,根据元素的类型(是数字、加号标志还是减号标志)进行相应的运算,计算出括号内的临时结果。 -
c. 计算最终结果:在处理完所有括号内的表达式后,我们继续处理栈中剩余的元素,最终得到整个表达式的计算结果。这一过程同样在
calculate
函数中完成,通过循环遍历解析后的数组,将元素依次压入栈中,并根据遇到的 ‘(’ 触发括号内的计算操作,最后栈顶元素即为最终的计算结果。
3. JavaScript 代码实现的详细解读
var calculate = function (s) {
if (s.length === 0) {
return 0;
}
let arr = pareToArray(s);
let stack = [];
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] !== '(') {
if (arr[i] === '-') {
stack.push(false);
} else if (arr[i] === '+') {
stack.push(true)
} else if (arr[i] === ')') {
stack.push(arr[i])
} else if (arr[i] !== ' ') {
stack.push(Number(arr[i]));
}
} else {
calculateWithoutBracket(stack);
}
}
calculateWithoutBracket(stack);
return stack.pop();
};
var pareToArray = function (s) {
if (s[0] === '-' || s[0] === '+') {
s = 0 + s;
}
let arr = [];
for (let i = 0; i < s.length; i++) {
if (s[i] === ' ') {
continue;
}
if (isNaN(Number(s[i]))) {
arr.push(s[i]);
} else {
if (isNaN(Number(arr[arr.length - 1]))) {
arr.push(s[i]);
} else {
arr[arr.length - 1] += s[i];
}
}
}
return arr;
}
var calculateWithoutBracket = function (stack) {
let tempSum = stack.pop();
while (stack.length !== 0 && stack[stack.length - 1] !== ')') {
let item = stack.pop();
if (item === false) {
tempSum -= stack.pop();
} else {
tempSum += stack.pop();
}
}
if (stack[stack.length - 1] === ')') {
stack.pop();
}
stack.push(tempSum);
}
3.1 calculate
函数
这个函数是整个程序的入口。首先,它会检查输入字符串是否为空,如果为空则直接返回 0。接着,调用pareToArray
函数将字符串解析为数组arr
。然后,初始化一个栈stack
,从数组arr
的末尾开始向前遍历。在遍历过程中,根据元素的类型进行不同的操作:如果是数字,则将其转换为数字类型后压入栈;如果是 ‘+’,则压入true
表示加号;如果是 ‘-’,则压入false
表示减号;如果是 ‘)’,则直接压入栈。当遇到 ‘(’ 时,调用calculateWithoutBracket
函数计算括号内的结果。最后,再次调用calculateWithoutBracket
函数处理栈中剩余的元素,并返回栈顶元素,即最终的计算结果。
3.2 pareToArray
函数
该函数负责将输入字符串解析为数组。如果字符串以 ‘-’ 或 ‘+’ 开头,会在前面补 0。然后遍历字符串,跳过空格字符。对于非数字字符,直接将其添加到数组中;对于数字字符,会判断数组中前一个元素是否也是数字,如果是,则将当前数字字符追加到前一个数字字符串上,最终返回解析后的数组。
3.3 calculateWithoutBracket
函数
这个函数用于计算括号内的结果。首先从栈中弹出一个元素作为临时和tempSum
。然后进入循环,只要栈不为空且栈顶元素不是 ‘)’,就继续从栈中弹出元素。如果弹出的元素是false
(表示减号),则从栈中再弹出一个元素并从tempSum
中减去;如果弹出的元素是true
(表示加号),则从栈中再弹出一个元素并加到tempSum
上。当循环结束后,如果栈顶元素是 ‘)’,则将其弹出,最后将计算得到的tempSum
压入栈中。
4. 代码的优化思考与展望
虽然当前的代码已经能够正确解决基本计算器问题,但仍有一些可以优化的地方。例如,在解析字符串部分,可以考虑使用更高效的字符串处理方法,减少不必要的判断和操作。在计算过程中,栈的使用可以进一步优化,以减少空间复杂度。另外,对于边界情况和异常输入的处理还可以更加完善,比如处理非法的表达式格式等。
通过对 LeetCode 224 题的分析与解答,我们不仅掌握了如何解决一个复杂的算术表达式计算问题,更重要的是,在这个过程中提升了对字符串解析、栈数据结构以及算法设计的理解和运用能力。希望这篇博客能够为正在学习算法的你提供一些帮助和启发,让我们一起在算法的世界中不断探索前行。