We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前置知识:前缀、中缀和后缀表达式。
以224. 基本计算器来作为例子说明中缀转后缀的过程。
难点:
在编程语言中 1++1 是不合法的,因为 ++ 本身是一个运算符,不能有二义性。而 1+-1 是合法的,与 -1+1 等价。在上述题目中,虽然两者均不合法,无需考虑,但是,+2+(-2) 这样写是合法的,是有点麻烦的地方。
1++1
++
1+-1
-1+1
+2+(-2)
对于中缀转换为后缀之后的样子,可以举个例子:1+2*(3+4*(5+6*7))。对于后缀表达式,无需使用括号来表达优先级,从左到右第一个操作符就是第一个需要执行的运算,然后在找下一个最左的操作符,以此类推。所以在中缀转后缀的过程中,我们就需要找出中缀表达式中第一个执行的运算符,把它放到后缀表达式的最左端即可。在 1+2*(3+4*(5+6*7)) 中,第一个被执行的应该是最右端的 6*7 其他的都需要等待它出了结果才能继续计算。下一个是 5+,以此类推,最后得到的后缀表达式应该是 1234567*+*+*+。对于后缀表达式的计算,有简单一些的150. 逆波兰表达式求值题目。
1+2*(3+4*(5+6*7))
6*7
5+
1234567*+*+*+
接下来就可以开始考虑转换了。
1+(-1)
综上:
// 后缀表达式的计算函数,可以直接用来解:https://leetcode.cn/problems/evaluate-reverse-polish-notation/ function evalRPN (tokens) { const stack = [] for (let i = 0; i < tokens.length; i++) { const token = tokens[i] if ( token !== '+' && token !== '-' && token !== '*' && token !== '/' ) { stack.push(Number(token)) } else { const right = stack.pop() const left = stack.pop() let result = 0 if (token === '+') { result = left + right } else if (token === '-') { result = left - right } else if (token === '*') { result = left * right } else { result = left / right if (result >= 0) { result = Math.floor(result) } else { result = Math.ceil(result) } } stack.push(result) } } return stack.pop() } function getPriority (op) { if (op === '+' || op === '-') return 1 if (op === '*' || op === '/') return 2 return 0 } // 补零 function pushZero (s, i, tokens) { let j = i + 1 while (s[j] === ' ') j++ const nextChar = s[j] if (getPriority(nextChar) !== 0) { tokens.push('0') } } // 与“150. 逆波兰表达式求值”的输入为解析好的 token 数组不同,这一题的输入是字符串表达式,需要自己解析其中的数字,数字不只有一位数字。 function parseNum (s, i, tokens) { let val = 0 let j = i while (s[j] >= '0' && s[j] <= '9') { val = val * 10 + Number(s[j]) j++ } if (j === i) { return -1 } tokens.push(String(val)) return j } /** * @param {string} s * @return {number} */ var calculate = function(s) { const tokens = [] const opsStack = [] pushZero(s, -1, tokens) for (let i = 0; i < s.length; i++) { const numResult = parseNum(s, i, tokens) if (numResult !== -1) { i = numResult - 1 continue } const char = s[i] if (char === ' ') continue if (char === '(') { opsStack.push(char) pushZero(s, i, tokens) continue } if (char === ')') { while (opsStack.length !== 0 && opsStack[opsStack.length - 1] !== '(') { tokens.push(opsStack.pop()) } opsStack.pop() continue } while (opsStack.length !== 0 && getPriority(opsStack[opsStack.length - 1]) >= getPriority(char)) { tokens.push(opsStack.pop()) } opsStack.push(char) } while (opsStack.length !== 0) { tokens.push(opsStack.pop()) } return evalRPN(tokens) };
为了拆分模块,难免会造成一些性能上损失,但总体复杂度不变,依然是 O(n),所以这不是什么大问题。
如果对上面的思路很熟悉了,那借用一部分来直接递归也是可以的。当前的操作符能否执行,取决于下一个操作符的优先级是否高于当前的。那么如果下一个优先级更高,甚至遇到括号,就直接递归好了。
function getPriority (op) { if (op === '+' || op === '-') return 1 if (op === '*' || op === '/') return 2 return 0 } function parseNum (s, i, tokens) { let val = 0 let j = i while (s[j] >= '0' && s[j] <= '9') { val = val * 10 + Number(s[j]) j++ } if (j === i) { return -1 } tokens.push(val) return j } export function getBracketEnd (s, index) { const stack = ['('] for (let i = index + 1; i < s.length; i++) { if (s[i] === '(') { stack.push('(') } else if (s[i] === ')') { stack.pop() if (stack.length === 0) { return i } } } } export function parse (s, start, end) { const tokens = [] for (let i = start; i <= end; i++) { const numResult = parseNum(s, i, tokens) if (numResult !== -1) { i = numResult - 1 continue } const char = s[i] if (char === ' ') continue if (char === '(') { const bracketEndIndex = getBracketEnd(s, i) const bracketTokens = parse(s, i + 1, bracketEndIndex - 1) tokens.push(bracketTokens) i = bracketEndIndex continue } tokens.push(char) } return tokens } function cal (left, op, right) { if (op === '+') { return left + right } else if (op === '-') { return left - right } else if (op === '*') { return left * right } else { const result = left / right if (result >= 0) { return Math.floor(result) } else { return Math.ceil(result) } } } function calTokens (tokens) { let val = tokens[0] let i = 1 // 处理类似"-1 + 1"的情况 if (typeof val === 'string') { val = 0 i = 0 } else if (typeof val === 'object') { // 处理类似"(1 + 1)"的情况 val = calTokens(val) } while (i < tokens.length) { const token = tokens[i] if (getPriority(tokens[i + 2]) > getPriority(token)) { let right = calTokens(tokens, i + 1)[0] return cal(val, token, right) } else { let right = tokens[i + 1] if (typeof right === 'object') { right = calTokens(right) } if (typeof val === 'object') { val = calTokens(val) } val = cal(val, token, right) i = i + 2 } } return val } /** * @param {string} s * @return {number} */ var calculate = function(s) { const tokens = parse(s, 0, s.length - 1) // 在这里打印 tokens,就很容易明白了 // console.log(tokens) return calTokens(tokens) };
我写的这个思路,由于会有这两行代码:
const bracketEndIndex = getBracketEnd(s, i) const bracketTokens = parse(s, i + 1, bracketEndIndex - 1)
确认括号的结尾之后还有再解析一遍,所以会比较慢,复杂度是 O(mn),m 是括号的数量,n 是字符串的长度。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
以224. 基本计算器来作为例子说明中缀转后缀的过程。
难点:
在编程语言中
1++1
是不合法的,因为++
本身是一个运算符,不能有二义性。而1+-1
是合法的,与-1+1
等价。在上述题目中,虽然两者均不合法,无需考虑,但是,+2+(-2)
这样写是合法的,是有点麻烦的地方。对于中缀转换为后缀之后的样子,可以举个例子:
1+2*(3+4*(5+6*7))
。对于后缀表达式,无需使用括号来表达优先级,从左到右第一个操作符就是第一个需要执行的运算,然后在找下一个最左的操作符,以此类推。所以在中缀转后缀的过程中,我们就需要找出中缀表达式中第一个执行的运算符,把它放到后缀表达式的最左端即可。在1+2*(3+4*(5+6*7))
中,第一个被执行的应该是最右端的6*7
其他的都需要等待它出了结果才能继续计算。下一个是5+
,以此类推,最后得到的后缀表达式应该是1234567*+*+*+
。对于后缀表达式的计算,有简单一些的150. 逆波兰表达式求值题目。接下来就可以开始考虑转换了。
1+(-1)
的情况,可能会出现的地方有两个,一个是整个表达式开始的第一个字符,另一个出现左括号之后的第一个字符。可以通过在他们之前补零来解决。综上:
为了拆分模块,难免会造成一些性能上损失,但总体复杂度不变,依然是 O(n),所以这不是什么大问题。
直接递归
如果对上面的思路很熟悉了,那借用一部分来直接递归也是可以的。当前的操作符能否执行,取决于下一个操作符的优先级是否高于当前的。那么如果下一个优先级更高,甚至遇到括号,就直接递归好了。
我写的这个思路,由于会有这两行代码:
确认括号的结尾之后还有再解析一遍,所以会比较慢,复杂度是 O(mn),m 是括号的数量,n 是字符串的长度。
The text was updated successfully, but these errors were encountered: