1597. Build Binary Expression Tree From Infix Expression
Explanation
The given problem asks to construct a binary tree from an infix expression. An infix expression is an algebraic expression where operators appear in-between two operands.
The task requires determining the hierarchy of operations. Multiplication and division have higher priority than addition and subtraction. This is generally enforced using parentheses, but if they aren't present it's important to follow the order of precedence.
Consider an expression like: "2-3/(5*2)+1". Here, the operations must be organized in the following order:
- Multiplication: 5*2
- Division: 3/(5*2)
- Subtraction: 2-3/(5*2)
- Addition: 2-3/(5*2)+1
In binary tree representation, operators are internal nodes with 2 children, while operands (numbers) are leaf nodes with no children.
Solution Walkthrough
This problem involves the use of two stack data structures, nodes
and ops
. The nodes
stack stores the operands and the expressions created so far. The ops
stack keeps track of the operators appeared in the infix expression.
The solution makes use of algorithm postfixed expression (also known as Reverse Polish Notation - RPN), which is a mathematical notation where every operator follows all of its operands.
For each character in the string:
- If it's a number, create a new node and add it to the
nodes
stack. - If it's an operator, then:
- While the operator at the top of the
ops
stack has a priority equal or greater than the current operator, pop the operator and two nodes from the stacks and build a new node. Add this new node to thenodes
stack. - Push the current operator onto the
ops
stack.
- While the operator at the top of the
- If it's a '(', just push it onto the
ops
stack. - If it's a ')', keep popping operators and building new nodes until we find a '(' in the
ops
stack. This will construct the binary tree for the expression inside the parentheses.
Once all characters have been processed, keep popping operators from the ops
stack, building new nodes, until the ops
stack is empty. The nodes
stack will now have one item, which is the root of the binary tree.
Lastly, we just return the remaining node in the nodes
stack which is the root of our binary expression tree.
Python
class Solution: def expTree(self, s: str) -> 'Node': def helper(stack): node = stack.pop() if node.val.isdigit(): return node node.right = helper(stack) node.left = helper(stack) return node stack = [] prec = {'+': 0, '-': 0, '*': 1, '/': 1} num = "" for c in reversed("("+s+")"): if c.isdigit(): num += c stack.append(Node(int(num))) num = "" elif c in prec: while len(stack) > 1 and prec[stack[-2].val] >= prec[c]: stack.append(Node(stack.pop(), stack.pop(), stack.pop())) stack.append(Node(c)) elif c == ")": stack.append(Node(c)) elif c == "(" and len(stack) > 1: while stack[-2].val != ")": stack.append(Node(stack.pop(), stack.pop(), stack.pop())) stack.pop() return helper(stack)
Java
java class Solution { public Node expTree(String s) { Stack<Node> nodes = new Stack<>(); Stack<Character> ops = new Stack<>(); for(char c : s.toCharArray()) if(Character.isDigit(c)) nodes.push(new Node(String.valueOf(c))); else if(c == '(') ops.push(c); else if(c == ')') { while(ops.peek() != '(') { nodes.push(build(ops.pop(), nodes.pop(), nodes.pop())); } ops.pop(); } else { while(!ops.isEmpty() && ops.peek() != '(' && precedence(ops.peek()) >= precedence(c)) { nodes.push(build(ops.pop(), nodes.pop(), nodes.pop())); } ops.push(c); } while(!ops.isEmpty()) nodes.push(build(ops.pop(), nodes.pop(), nodes.pop())); return nodes.pop(); } public static int precedence(char c){ if(c == '+' || c == '-') return 1; else if(c == '*' || c == '/') return 2; else return 0; } public static Node build(char c, Node node1, Node node2) { return new Node(String.valueOf(c), node2, node1); } }
C++
cpp
class Solution {
private:
Node* buildNode(char op, Node* right, Node* left) {
return new Node(op, left, right);
}
// Returns true if op1 is a operator and priority(op1) >= priority(op2)
bool compare(char op1, char op2) {
if (op1 == '(' || op1 == ')')
return false;
return op1 == '*' || op1 == '/' || op2 == '+' || op2 == '-';
}
char pop(stack<char>& ops) {
const char op = ops.top();
ops.pop();
return op;
}
Node* pop(stack<Node*>& nodes) {
Node* node = nodes.top();
nodes.pop();
return node;
}
public:
Node* expTree(string s) {
stack<Node*> nodes; // Stores nodes.
stack<char> ops; // Stores operators and parentheses.
for (const char c : s)
if (isdigit(c)) {
nodes.push(new Node(c));
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
while (ops.top() != '(')
nodes.push(buildNode(pop(ops), pop(nodes), pop(nodes)));
ops.pop(); // Remove '('.
} else if (c == '+' || c == '-' || c == '*' || c == '/') {
while (!ops.empty() && compare(ops.top(), c))
nodes.push(buildNode(pop(ops), pop(nodes), pop(nodes)));
ops.push(c);
}
while (!ops.empty())
nodes.push(buildNode(pop(ops), pop(nodes), pop(nodes)));
return nodes.top();
}
};
JavaScript ES6
js function expTree(s) { var ops = [], nodes = []; for (const c of s) { if ('0' <= c && c <= '9') nodes.push(new Node(c)); else if (c == '(') ops.push(c); else if (c == ')') while (ops[ops.length - 1] != '(') nodes.push(build()); else { while (ops.length && opsStillGoes()) nodes.push(build()); ops.push(c); } } while (ops.length) nodes.push(build()); return nodes[0]; function opsStillGoes() { var c = ops[ops.length - 1], c2 = ops[ops.length - 2]; if (c == '(' || c2 == '(' || (c == '+' || c == '-') && (c2 == '*' || c2 == '/')) return false; return true; } function build() { var c = ops.pop(), r = nodes.pop(), l = nodes.pop(); if (c == '(') return r; return new Node(c, l, r); } }
Here, the build
function checks if there are nodes available for creation of subtree by popping ops
and checking the operator. If '(', it means an expression within parenthesis has been calculated and placed in nodes
, so avoid creating unnecessary Node and instead return the precalculated Node. If any other operator, it pops two latest Nodes from nodes
to create a new Node with left - operator - right hierarchy and return this to be pushed back to nodes
.
C#
csharp
public class Solution {
public Node ExpTree(string s) {
Stack < char > ops = new Stack < char > ();
Stack < Node > nodes = new Stack < Node > ();
foreach(char c in s.ToCharArray()) {
if (c >= '0' && c <= '9') nodes.Push(new Node(c.ToString()));
else if (c == '(') ops.Push(c);
else if (c != ')') {
while (ops.Count > 0 && ops.Peek() != '(' && Precedence(ops.Peek()) >= Precedence(c)) nodes.Push(BuildNode(ops.Pop(), nodes.Pop(), nodes.Pop()));
ops.Push(c);
} else {
while (ops.Peek() != '(') nodes.Push(BuildNode(ops.Pop(), nodes.Pop(), nodes.Pop()));
ops.Pop();
}
}
while (ops.Count > 0) nodes.Push(BuildNode(ops.Pop(), nodes.Pop(), nodes.Pop()));
return nodes.Peek();
}
public Node BuildNode(char op, Node right, Node left) {
return new Node(op.ToString(), left, right);
}
public int Precedence(char op) {
if (op == '+' || op == '-') return 1;
else if (op == '*' || op == '/') return 2;
else return 0;
}
}
In this C# solution, we iterate through s with foreach loop and break down discriminative components: numbers, operators and parentheses. Operators are kept in ops stack and node characters are kept in nodes stack. We use the function Precedence to determine 'weight' of the operators so as to resolve which operator to evaluate first. After the major calculations inside the respective stacks, now we are left with rest of the nodes in the nodes stack. The rest of the all nodes are removed from the stack and with their corresponding operator from the ops stack, new nodes are created and pushed back to the nodes stack. In the end the final expression tree should be left in the nodes stack which is returned to complete the requirement of the problem.
Note
Please ensure a thorough understanding of infix expressions, reversed polish notation, and tree data structures is key to solving this problem.It should also be noted that the stack data structure plays a crucial role in constructing a binary tree from infix expressions. Stacks are simple yet powerful data structures that follow the LIFO (last-in-first-out) principle. This allows us to temporarily hold operators and operands in a sequence, and retrieve them as soon as their respective counterparts are available.
Let's take a look at an example in order to better understand how stacks are used in the solution.
Consider the infix expression: "(3+4)*2". Here's how the solution works:
- Following the algorithm, push
(
ontoops
stack. 3
is a digit, create a new node and push it ontonodes
stack.+
is an operator, push it ontoops
stack.- Then,
4
is a digit, create a new node and push it ontonodes
stack. )
indicates matching open parenthesis '(', so pop two nodes and an operator from the stacks, build a new node, and push it ontonodes
stack.*
is an operator, push it ontoops
stack.- Finally,
2
is a digit, create a new node and push it ontonodes
stack.
At the end of the expressions, ops
stack is not empty, so pop two nodes from nodes
stack, and one operator from ops
stack, build a new node and push it back onto nodes
stack. Now, nodes
stack contains the root of binary expression tree.
In conclusion, handling infix expressions to construct a binary tree involves carefully working with stack data structures, dealing with the operator precedence and handling parenthesis in the expression. Each operation in the expression becomes a node in the binary tree, with the operand or sub-expressions as their child nodes, thus forming an entire binary tree structure that represents the original expression. With practice and comprehension of these basic data structures and algorithms, such problems can be dealt with effectively.
Remember, problem solving in computer science typically involves breaking down a complex problem into smaller, manageable problems, and then solving each of these smaller problems. So, keep practicing and happy coding!
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorWhat is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!