1106. Parsing A Boolean Expression
Problem Description
You are given a string representing a boolean expression that needs to be evaluated to either true
or false
.
The expression can contain the following elements:
't'
- represents the boolean valuetrue
'f'
- represents the boolean valuefalse
'!(subExpr)'
- represents the logical NOT operation on the inner expressionsubExpr
'&(subExpr₁, subExpr₂, ..., subExprₙ)'
- represents the logical AND operation on n inner expressions (where n ≥ 1)'|(subExpr₁, subExpr₂, ..., subExprₙ)'
- represents the logical OR operation on n inner expressions (where n ≥ 1)
Each operation follows standard boolean logic:
- NOT (
!
) flips the boolean value (true becomes false, false becomes true) - AND (
&
) returns true only if all sub-expressions are true - OR (
|
) returns true if at least one sub-expression is true
The sub-expressions within AND and OR operations are separated by commas and enclosed in parentheses. The expression is guaranteed to be valid and follow the given rules.
Your task is to parse and evaluate the given boolean expression string and return the final boolean result.
For example:
"t"
evaluates totrue
"!(f)"
evaluates totrue
(NOT false = true)"&(t,f)"
evaluates tofalse
(true AND false = false)"|(f,t)"
evaluates totrue
(false OR true = true)
Intuition
When we look at this boolean expression, we notice it has a nested structure with operators and parentheses. This immediately suggests we need a way to handle the innermost expressions first before evaluating the outer ones - similar to how we evaluate arithmetic expressions with parentheses.
The key insight is that whenever we encounter a closing parenthesis ')'
, we've reached the end of a complete sub-expression that needs to be evaluated. At this point, we should evaluate everything between this closing parenthesis and its corresponding operator (which comes right before the opening parenthesis).
A stack is perfect for this because:
- We can push characters as we encounter them
- When we hit a
')'
, we can pop backwards to collect all the boolean values that need to be evaluated - The operator will be right after these boolean values (going backwards in the stack)
The evaluation process becomes straightforward:
- For
'!'
, we just need to flip the single boolean value - For
'&'
, if there's any'f'
among the values, the result is'f'
, otherwise't'
- For
'|'
, if there's any't'
among the values, the result is't'
, otherwise'f'
After evaluating a sub-expression, we replace it with its result ('t'
or 'f'
) by pushing this result back onto the stack. This way, the evaluated result can participate in outer expressions as a simple boolean value.
By processing the string character by character and evaluating sub-expressions as we complete them, we gradually simplify the entire expression until we're left with a single boolean value on the stack - our final answer.
Solution Approach
We implement the solution using a stack-based approach to parse and evaluate the boolean expression from left to right.
Algorithm Steps:
-
Initialize a stack to store characters as we traverse the expression.
-
Traverse each character
c
in the expression:- If
c
is one of't'
,'f'
,'!'
,'&'
, or'|'
, push it directly onto the stack - If
c
is a comma','
or opening parenthesis'('
, skip it (we don't need them for evaluation) - If
c
is a closing parenthesis')'
, it signals the end of a sub-expression that needs evaluation
- If
-
When encountering
')'
, evaluate the sub-expression:- Initialize counters:
t = 0
andf = 0
to count true and false values - Pop elements from the stack while the top element is either
't'
or'f'
:- If popped element is
't'
, incrementt
- If popped element is
'f'
, incrementf
- If popped element is
- After collecting all boolean values, pop the operator from the stack
- Based on the operator, compute the result:
- For
'!'
: Result is't'
iff > 0
(NOT false = true), else'f'
- For
'&'
: Result is'f'
iff > 0
(any false makes AND false), else't'
- For
'|'
: Result is't'
ift > 0
(any true makes OR true), else'f'
- For
- Push the computed result back onto the stack
- Initialize counters:
-
After processing the entire expression, only one character remains in the stack - this is our final result. Return
True
if it's't'
, otherwiseFalse
.
Example Walkthrough:
For expression "&(t,!(f))"
:
- Push
'&'
→ stack:['&']
- Skip
'('
- Push
't'
→ stack:['&', 't']
- Skip
','
- Push
'!'
→ stack:['&', 't', '!']
- Skip
'('
- Push
'f'
→ stack:['&', 't', '!', 'f']
- Encounter first
')'
:- Pop
'f'
(f=1) - Pop
'!'
(operator) - Evaluate: NOT false = true, push
't'
- Stack:
['&', 't', 't']
- Pop
- Encounter second
')'
:- Pop
't'
,'t'
(t=2) - Pop
'&'
(operator) - Evaluate: true AND true = true, push
't'
- Stack:
['t']
- Pop
- Return
True
since stack[0] is't'
The time complexity is O(n)
where n is the length of the expression, as we process each character once. The space complexity is also O(n)
for the stack storage.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the expression "|(f,&(t,f))"
step by step to see how our stack-based solution works.
Initial expression: "|(f,&(t,f))"
Step-by-step execution:
-
Character
'|'
: Push onto stack- Stack:
['|']
- Stack:
-
Character
'('
: Skip (we don't need opening parentheses)- Stack:
['|']
- Stack:
-
Character
'f'
: Push onto stack- Stack:
['|', 'f']
- Stack:
-
Character
','
: Skip (we don't need commas)- Stack:
['|', 'f']
- Stack:
-
Character
'&'
: Push onto stack- Stack:
['|', 'f', '&']
- Stack:
-
Character
'('
: Skip- Stack:
['|', 'f', '&']
- Stack:
-
Character
't'
: Push onto stack- Stack:
['|', 'f', '&', 't']
- Stack:
-
Character
','
: Skip- Stack:
['|', 'f', '&', 't']
- Stack:
-
Character
'f'
: Push onto stack- Stack:
['|', 'f', '&', 't', 'f']
- Stack:
-
Character
')'
: Time to evaluate the inner expression!- Pop
'f'
→ f = 1 - Pop
't'
→ t = 1 - Pop
'&'
(operator) - Evaluate:
&(t,f)
= true AND false = false - Push result
'f'
back - Stack:
['|', 'f', 'f']
- Pop
-
Character
')'
: Time to evaluate the outer expression!- Pop
'f'
→ f = 1 - Pop
'f'
→ f = 2 - Pop
'|'
(operator) - Evaluate:
|(f,f)
= false OR false = false - Push result
'f'
back - Stack:
['f']
- Pop
Final Result: The stack contains 'f'
, so we return False
.
This example demonstrates how:
- We process characters left to right
- Closing parentheses trigger evaluation of sub-expressions
- Inner expressions are evaluated first (the
&(t,f)
before the outer|
) - Each evaluation replaces a complex sub-expression with a simple boolean value
- The process continues until we have a single boolean result
Solution Implementation
1class Solution:
2 def parseBoolExpr(self, expression: str) -> bool:
3 """
4 Evaluates a boolean expression string containing operators and boolean values.
5
6 Args:
7 expression: A string containing boolean expression with:
8 - 't' for true, 'f' for false
9 - '!' for NOT, '&' for AND, '|' for OR
10 - Parentheses for grouping
11
12 Returns:
13 Boolean result of the evaluated expression
14 """
15 stack = []
16
17 for char in expression:
18 # Push operators and boolean values onto the stack
19 if char in 'tf!&|':
20 stack.append(char)
21
22 # When we encounter a closing parenthesis, evaluate the sub-expression
23 elif char == ')':
24 # Count true and false values in the current sub-expression
25 true_count = 0
26 false_count = 0
27
28 # Pop all boolean values until we reach an operator
29 while stack[-1] in 'tf':
30 if stack[-1] == 't':
31 true_count += 1
32 else: # stack[-1] == 'f'
33 false_count += 1
34 stack.pop()
35
36 # Pop the operator and evaluate based on its type
37 operator = stack.pop()
38
39 # Evaluate based on the operator type
40 if operator == '!':
41 # NOT operator: returns opposite of the single operand
42 result = 't' if false_count > 0 else 'f'
43 elif operator == '&':
44 # AND operator: returns false if any operand is false
45 result = 'f' if false_count > 0 else 't'
46 elif operator == '|':
47 # OR operator: returns true if any operand is true
48 result = 't' if true_count > 0 else 'f'
49
50 # Push the result back onto the stack
51 stack.append(result)
52
53 # Skip opening parentheses and commas (they don't affect evaluation)
54 # These characters are implicitly ignored by not handling them
55
56 # The final result is the only element left in the stack
57 return stack[0] == 't'
58
1class Solution {
2 public boolean parseBoolExpr(String expression) {
3 // Stack to store operators and boolean values during evaluation
4 Deque<Character> stack = new ArrayDeque<>();
5
6 // Process each character in the expression
7 for (char currentChar : expression.toCharArray()) {
8 // Skip parentheses and commas, push everything else to stack
9 if (currentChar != '(' && currentChar != ')' && currentChar != ',') {
10 stack.push(currentChar);
11 }
12 // When encountering closing parenthesis, evaluate the subexpression
13 else if (currentChar == ')') {
14 // Count true and false values in current subexpression
15 int trueCount = 0;
16 int falseCount = 0;
17
18 // Pop all boolean values until we reach the operator
19 while (stack.peek() == 't' || stack.peek() == 'f') {
20 if (stack.peek() == 't') {
21 trueCount++;
22 } else {
23 falseCount++;
24 }
25 stack.pop();
26 }
27
28 // Pop the operator for this subexpression
29 char operator = stack.pop();
30
31 // Evaluate the subexpression based on the operator
32 char result = 'f'; // Default result is false
33
34 // Apply the operator logic:
35 // NOT: true if operand is false
36 // AND: true if all operands are true (no false values)
37 // OR: true if at least one operand is true
38 if ((operator == '!' && falseCount > 0) ||
39 (operator == '&' && falseCount == 0) ||
40 (operator == '|' && trueCount > 0)) {
41 result = 't';
42 }
43
44 // Push the result back to stack for further evaluation
45 stack.push(result);
46 }
47 }
48
49 // The final result is at the top of the stack
50 return stack.peek() == 't';
51 }
52}
53
1class Solution {
2public:
3 bool parseBoolExpr(string expression) {
4 stack<char> stk;
5
6 // Process each character in the expression
7 for (char c : expression) {
8 // Push operators and boolean values onto stack, skip parentheses and commas
9 if (c != '(' && c != ')' && c != ',') {
10 stk.push(c);
11 }
12 // When encountering closing parenthesis, evaluate the sub-expression
13 else if (c == ')') {
14 int trueCount = 0;
15 int falseCount = 0;
16
17 // Count all boolean values until we reach the operator
18 while (stk.top() == 't' || stk.top() == 'f') {
19 if (stk.top() == 't') {
20 trueCount++;
21 } else {
22 falseCount++;
23 }
24 stk.pop();
25 }
26
27 // Get the operator for this sub-expression
28 char op = stk.top();
29 stk.pop();
30
31 // Evaluate based on the operator type
32 char result;
33 if (op == '!') {
34 // NOT operator: true if operand is false
35 result = (falseCount > 0) ? 't' : 'f';
36 } else if (op == '&') {
37 // AND operator: false if any operand is false
38 result = (falseCount > 0) ? 'f' : 't';
39 } else if (op == '|') {
40 // OR operator: true if any operand is true
41 result = (trueCount > 0) ? 't' : 'f';
42 }
43
44 // Push the result back onto stack
45 stk.push(result);
46 }
47 }
48
49 // The final result is at the top of the stack
50 return stk.top() == 't';
51 }
52};
53
1/**
2 * Parses and evaluates a boolean expression string
3 * @param expression - The boolean expression to evaluate
4 * @returns The boolean result of the expression
5 */
6function parseBoolExpr(expression: string): boolean {
7 const expressionString: string = expression;
8 const expressionLength: number = expressionString.length;
9 let currentIndex: number = 0;
10
11 /**
12 * Recursively evaluates the boolean expression using depth-first search
13 * @returns Array of boolean values from the current expression level
14 */
15 const evaluateExpression = (): boolean[] => {
16 const results: boolean[] = [];
17
18 // Process characters until we reach the end or a closing parenthesis
19 while (currentIndex < expressionLength) {
20 const currentChar: string = expressionString[currentIndex++];
21
22 // End of current expression level
23 if (currentChar === ')') {
24 break;
25 }
26
27 // Handle different operators and values
28 if (currentChar === '!') {
29 // NOT operator: negate the first result of the nested expression
30 results.push(!evaluateExpression()[0]);
31 } else if (currentChar === '|') {
32 // OR operator: return true if any nested value is true
33 results.push(evaluateExpression().some((value: boolean) => value));
34 } else if (currentChar === '&') {
35 // AND operator: return true if all nested values are true
36 results.push(evaluateExpression().every((value: boolean) => value));
37 } else if (currentChar === 't') {
38 // True literal
39 results.push(true);
40 } else if (currentChar === 'f') {
41 // False literal
42 results.push(false);
43 }
44 // Skip commas and opening parentheses (implicit in the logic)
45 }
46
47 return results;
48 };
49
50 // Start evaluation and return the first (and only) result
51 return evaluateExpression()[0];
52}
53
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the expression string.
The algorithm iterates through each character in the expression exactly once. For each character:
- Characters
't'
,'f'
,'!'
,'&'
,'|'
are pushed to the stack inO(1)
time - Character
')'
triggers evaluation where we pop elements from the stack. Each't'
or'f'
character is pushed once and popped once throughout the entire execution - The evaluation operations (counting
t
andf
, and determining the result) are allO(1)
per character processed
Since each character is processed at most twice (once when pushed, once when popped during evaluation), the total time complexity is O(n)
.
Space Complexity: O(n)
, where n
is the length of the expression string.
The space is used by the stack stk
. In the worst case, the stack could contain:
- All operands and operators before encountering any closing parenthesis
- For deeply nested expressions like
&(&(&(...)))
, the stack grows proportionally to the input size - The maximum stack size is bounded by the number of characters in the expression (excluding commas and opening parentheses which aren't stored)
Therefore, the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Operator Precedence Assumption
A common mistake is assuming that operators need to be evaluated in a specific order or that nested expressions require special handling. Developers might overcomplicate the solution by trying to build an expression tree or implement operator precedence rules.
Why it happens: In traditional expression evaluation, we often deal with operator precedence (e.g., multiplication before addition). Here, the parentheses explicitly define the evaluation order, making the problem simpler.
Solution: Trust the stack-based approach. The closing parenthesis naturally triggers evaluation at the right time, handling nesting automatically without explicit precedence rules.
2. Forgetting to Handle Single-Operand Cases
Some developers might assume that AND and OR operations always have multiple operands, but the problem states n ≥ 1, meaning single operands are valid:
&(t)
should returntrue
|(f)
should returnfalse
Why it happens: Test cases often show multiple operands, leading to assumptions about minimum operand counts.
Solution: The counting approach handles this naturally - if there's only one 't'
and no 'f'
values, AND returns true; if there's only one 'f'
and no 't'
values, OR returns false.
3. Stack Boundary Check Errors
When popping from the stack, forgetting to check if the element exists or using incorrect conditions can cause IndexError or infinite loops.
Incorrect approach:
while stack and stack[-1] != '(' : # Wrong! Looking for opening parenthesis if stack[-1] == 't': true_count += 1 stack.pop()
Why it happens: Confusion about what's actually on the stack - we never push opening parentheses, so we shouldn't look for them.
Solution: Check for boolean values specifically:
while stack[-1] in 'tf': # Correct - only pop boolean values if stack[-1] == 't': true_count += 1 else: false_count += 1 stack.pop()
4. Mishandling the NOT Operator
The NOT operator always has exactly one operand, but developers might:
- Try to apply NOT to multiple values
- Forget that
!(expression)
can have a complex expression inside, not just a single boolean
Why it happens: The NOT operator seems simple, but !(expression)
evaluates the entire expression first, then negates it.
Solution: The stack approach handles this correctly - by the time we process the closing parenthesis after !
, the inner expression has already been evaluated to a single boolean value.
5. Character Filtering Confusion
Some might explicitly handle commas and opening parentheses with unnecessary logic:
Overcomplicated approach:
if char == ',' or char == '(': continue # Explicit but unnecessary
Why it happens: Trying to be explicit about every character in the expression.
Solution: Simply don't handle these characters - the if-elif structure naturally ignores them:
if char in 'tf!&|': stack.append(char) elif char == ')': # evaluate # Commas and '(' are implicitly ignored
Which of the following is a good use case for backtracking?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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 assets algo monster recursion jpg You first call Ben and ask
Coding Interview 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
Want a Structured Path to Master System Design Too? Don’t Miss This!