Facebook Pixel

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 value true
  • 'f' - represents the boolean value false
  • '!(subExpr)' - represents the logical NOT operation on the inner expression subExpr
  • '&(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 to true
  • "!(f)" evaluates to true (NOT false = true)
  • "&(t,f)" evaluates to false (true AND false = false)
  • "|(f,t)" evaluates to true (false OR true = true)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We can push characters as we encounter them
  2. When we hit a ')', we can pop backwards to collect all the boolean values that need to be evaluated
  3. 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.

Learn more about Stack and Recursion patterns.

Solution Approach

We implement the solution using a stack-based approach to parse and evaluate the boolean expression from left to right.

Algorithm Steps:

  1. Initialize a stack to store characters as we traverse the expression.

  2. 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
  3. When encountering ')', evaluate the sub-expression:

    • Initialize counters: t = 0 and f = 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', increment t
      • If popped element is 'f', increment f
    • After collecting all boolean values, pop the operator from the stack
    • Based on the operator, compute the result:
      • For '!': Result is 't' if f > 0 (NOT false = true), else 'f'
      • For '&': Result is 'f' if f > 0 (any false makes AND false), else 't'
      • For '|': Result is 't' if t > 0 (any true makes OR true), else 'f'
    • Push the computed result back onto the stack
  4. After processing the entire expression, only one character remains in the stack - this is our final result. Return True if it's 't', otherwise False.

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']
  • Encounter second ')':
    • Pop 't', 't' (t=2)
    • Pop '&' (operator)
    • Evaluate: true AND true = true, push 't'
    • Stack: ['t']
  • 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 Evaluator

Example 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:

  1. Character '|': Push onto stack

    • Stack: ['|']
  2. Character '(': Skip (we don't need opening parentheses)

    • Stack: ['|']
  3. Character 'f': Push onto stack

    • Stack: ['|', 'f']
  4. Character ',': Skip (we don't need commas)

    • Stack: ['|', 'f']
  5. Character '&': Push onto stack

    • Stack: ['|', 'f', '&']
  6. Character '(': Skip

    • Stack: ['|', 'f', '&']
  7. Character 't': Push onto stack

    • Stack: ['|', 'f', '&', 't']
  8. Character ',': Skip

    • Stack: ['|', 'f', '&', 't']
  9. Character 'f': Push onto stack

    • Stack: ['|', 'f', '&', 't', 'f']
  10. 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']
  11. 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']

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 in O(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 and f, and determining the result) are all O(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 return true
  • |(f) should return false

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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More