Evaluate Boolean Binary Tree
EasyPrompt
You are given the root of a full binary tree with the following properties:
- Leaf nodes have either the value
0or1, where0representsFalseand1representsTrue. - Non-leaf nodes have either the value
2or3, where2represents the booleanORand3represents the booleanAND.
The evaluation of a node is as follows:
- If the node is a leaf node, the evaluation is the value of the node, i.e.
TrueorFalse. - Otherwise, evaluate the node's two children and apply the boolean operation of its value with the children's evaluations.
Return the boolean result of evaluating the root node.
A full binary tree is a binary tree where each node has either 0 or 2 children.
A leaf node is a node that has zero children.
Example 1:
Input: root = [2,1,3,null,null,0,1]
Output: true
Explanation: The above diagram illustrates the evaluation process.
The AND node evaluates to False AND True = False.
The OR node evaluates to True OR False = True.
The root node evaluates to True, so we return true.Example 2:
Input: root = [0]
Output: false
Explanation: The root node is a leaf node and it evaluates to false, so we return false.
Constraints:
- The number of nodes in the tree is in the range
[1, 1000]. 0 <= Node.val <= 3- Every node has either
0or2children. - Leaf nodes have a value of
0or1. - Non-leaf nodes have a value of
2or3.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach avoids recursion by using a stack to manually manage the traversal of the tree in a post-order fashion. A map is used to store the evaluated boolean results of the subtrees, so we know when a parent node's children have been processed and it's ready for evaluation.
Algorithm
- Initialize a
stackand push therootnode. - Initialize a
HashMap<TreeNode, Boolean>namedevaluatedto store the results of evaluated subtrees. - Loop while the
stackis not empty:- a. Peek at the top node,
node. - b. If
nodeis a leaf, pop it, store its boolean value (node.val == 1) inevaluated, and continue. - c. Check if both of
node's children are present as keys in theevaluatedmap. - d. If yes, pop
node, retrieve the children's results from the map, computenode's result, and store it in theevaluatedmap. - e. If no, push the right child (if not evaluated) and then the left child (if not evaluated) onto the stack to ensure they are processed first.
- a. Peek at the top node,
- After the loop terminates, the result for the
rootnode will be in theevaluatedmap. Returnevaluated.get(root).
Walkthrough
The algorithm simulates a post-order traversal, which is necessary because a parent node's value can only be computed after its children's values are known. We use a stack for the traversal and a HashMap called evaluated to store the boolean result for any node that has been computed.
The main loop continues as long as the stack is not empty. In each iteration, we look at the node at the top of the stack (peek()).
- If the node is a leaf, we can evaluate it immediately (
truefor value 1,falsefor 0), store the result in ourevaluatedmap, and pop the node from the stack. - If the node is an internal node, we check if both its children have already been evaluated by looking them up in the
evaluatedmap.- If both children are in the map, we can evaluate the current node. We pop it from the stack, retrieve the children's boolean values from the map, apply the
OR(value 2) orAND(value 3) operation, and store the result for the current node in theevaluatedmap. - If either child has not been evaluated yet, we push the unevaluated children onto the stack (right child first, then left child) so they will be processed before the current node.
- If both children are in the map, we can evaluate the current node. We pop it from the stack, retrieve the children's boolean values from the map, apply the
The process continues until the root node is evaluated. The final answer is the value associated with the root in the evaluated map.
import java.util.Stack;import java.util.Map;import java.util.HashMap; /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public boolean evaluateTree(TreeNode root) { Stack<TreeNode> stack = new Stack<>(); stack.push(root); Map<TreeNode, Boolean> evaluated = new HashMap<>(); while (!stack.isEmpty()) { TreeNode node = stack.peek(); // Case 1: Node is a leaf if (node.left == null && node.right == null) { stack.pop(); evaluated.put(node, node.val == 1); continue; } // Check if children have been evaluated boolean leftChildEvaluated = evaluated.containsKey(node.left); boolean rightChildEvaluated = evaluated.containsKey(node.right); // Case 2: Children have been evaluated, so evaluate the current node if (leftChildEvaluated && rightChildEvaluated) { stack.pop(); boolean leftVal = evaluated.get(node.left); boolean rightVal = evaluated.get(node.right); if (node.val == 2) { // OR evaluated.put(node, leftVal || rightVal); } else { // AND (val == 3) evaluated.put(node, leftVal && rightVal); } } else { // Case 3: Children not yet evaluated, push them to the stack if (!rightChildEvaluated) { stack.push(node.right); } if (!leftChildEvaluated) { stack.push(node.left); } } } return evaluated.get(root); }}Complexity
Time
O(N), where N is the number of nodes in the tree. Each node is pushed onto the stack and processed a constant number of times.
Space
O(N). The `evaluated` map stores a result for every node in the tree, leading to O(N) space. The stack also contributes up to O(H) space, where H is the tree height, but this is dominated by the map's space usage.
Trade-offs
Pros
Avoids recursion, preventing potential stack overflow errors on extremely deep trees.
Can be more performant in some environments by avoiding the overhead of recursive function calls.
Cons
More complex to implement and reason about compared to the recursive solution.
Requires O(N) auxiliary space for the map, which is less space-efficient than the recursive approach's O(H) space.
This is the most natural and elegant approach for this problem. Since the evaluation of any non-leaf node depends on the evaluation of its children, a post-order traversal is a perfect fit. We recursively evaluate the left and right subtrees and then combine their results at the parent node.
Algorithm
- Define a recursive function
evaluateTree(node). - Base Case: If
nodeis a leaf (node.left == null), returntrueifnode.val == 1, elsefalse. - Recursive Step:
- a. Call
evaluateTree(node.left)to get the result of the left subtree. - b. Call
evaluateTree(node.right)to get the result of the right subtree. - c. If
node.valis 2 (OR), return the logical OR of the children's results. - d. If
node.valis 3 (AND), return the logical AND of the children's results.
- a. Call
- The initial call
evaluateTree(root)will return the final answer.
Walkthrough
The solution is a recursive function that takes a TreeNode as input and returns its boolean evaluation.
Base Case: The recursion stops at the leaf nodes. If a node is a leaf (i.e., node.left and node.right are null), its value is either 0 or 1. We return true if node.val == 1 and false if node.val == 0.
Recursive Step: For any internal node, we first make recursive calls to evaluate its left and right children.
boolean leftResult = evaluateTree(node.left);
boolean rightResult = evaluateTree(node.right);
Once we have the boolean results from both children, we apply the operation specified by the current node's value. If node.val is 2 (OR), we return leftResult || rightResult. If node.val is 3 (AND), we return leftResult && rightResult.
The initial call to this function with the root of the tree will trigger the evaluation of the entire tree, returning the final boolean result.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public boolean evaluateTree(TreeNode root) { // Base Case: The node is a leaf. if (root.left == null && root.right == null) { return root.val == 1; } // Recursive Step: Evaluate left and right children. boolean leftValue = evaluateTree(root.left); boolean rightValue = evaluateTree(root.right); // Combine results based on the current node's operation. if (root.val == 2) { // OR return leftValue || rightValue; } else { // AND (root.val == 3) return leftValue && rightValue; } }}Complexity
Time
O(N), where N is the number of nodes. The function visits each node in the tree exactly once.
Space
O(H), where H is the height of the tree. This space is used by the call stack during recursion. In the worst case of a skewed tree, H can be O(N). For a balanced tree, H is O(log N).
Trade-offs
Pros
Very simple, clean, and easy to understand.
The code structure directly mirrors the problem's recursive definition.
More space-efficient than the iterative approach using a map, especially for balanced trees.
Cons
For extremely deep trees (not an issue with the given constraints), it could potentially lead to a stack overflow error.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public boolean evaluateTree ( TreeNode root ) { return dfs ( root ); } private boolean dfs ( TreeNode root ) { if ( root . left == null && root . right == null ) { return root . val == 1 ; } boolean l = dfs ( root . left ), r = dfs ( root . right ); if ( root . val == 2 ) { return l || r ; } return l && r ; } }/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: bool evaluateTree ( TreeNode * root ) { return dfs ( root ); } bool dfs ( TreeNode * root ) { if ( ! root -> left && ! root -> right ) return root -> val ; bool l = dfs ( root -> left ), r = dfs ( root -> right ); if ( root -> val == 2 ) return l || r ; return l && r ; } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def evaluateTree ( self , root : Optional [ TreeNode ]) -> bool : def dfs ( root ): if root . left is None and root . right is None : return bool ( root . val ) l , r = dfs ( root . left ), dfs ( root . right ) return ( l or r ) if root . val == 2 else ( l and r ) return dfs ( root )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.