5 Best Ways to Check for Balanced Parentheses in an Expression with O(1) Space and O(N^2) Time Complexity in Python

πŸ’‘ Problem Formulation: In programming, ensuring that parentheses are balanced in an expression is a common task that is essential for syntactical correctness. The challenge is to develop a Python algorithm that verifies the balance of parentheses in an expression without consuming more than constant extra space (O(1)) and within a quadratic time complexity (O(N^2)), where N is the length of the expression. The goal is to confirm that each opening parenthesis has a corresponding closing one. For instance, the string “(a*(b+c))” is balanced, while “(a*(b+c)” is not.

Method 1: Brute Force Iterative Check

This brute-force method involves iteratively checking all sub-strings of the expression to ensure that each opening parenthesis is matched with a closing one. The function is_parentheses_balanced() scans through the string, counting and matching parentheses repeatedly. This method is simple but inefficient for large strings due to its O(N^2) time complexity.

Here’s an example:

def is_parentheses_balanced(expr):
    while '()' in expr or '[]' in expr or '{}' in expr:
        expr = expr.replace('()', '').replace('[]', '').replace('{}', '')
    return not expr

print(is_parentheses_balanced('(a*[b+c])*d'))

Output: True

This code snippet replaces all occurrences of matched parentheses with an empty string until no more pairs exist or the string is empty. This process continues recursively until no matching pairs are left. A boolean value of True is returned if no unmatched parentheses remain, indicating balanced parentheses.

Method 2: Counting Parentheses

Counting parentheses is a straightforward approach where the number of opening and closing parentheses are counted. If at any point the count of closing parentheses exceeds the opening ones, or the ending counts are unequal, the parentheses are imbalanced. The limitation is that this method cannot handle nested structures efficiently.

Here’s an example:

def are_parentheses_balanced(expr):
    count = 0
    for char in expr:
        if char == '(':
            count += 1
        elif char == ')':
            count -= 1
        if count < 0:
            return False
    return count == 0

print(are_parentheses_balanced('(((a+b))*(c-d))'))

Output: True

In this code, we iterate over each character in the expression. We increment the count for each opening parenthesis and decrement for each closing parenthesis. If the count drops below zero or isn’t zero at the end, the parentheses are not balanced.

Method 3: Using Stack to Check for Nested Parentheses

The stack based method is an effective approach that can handle nested parentheses. It involves iterating over the input string, pushing opening parentheses onto the stack, and popping them when a corresponding closing parenthesis is encountered. This works well for balanced parentheses but requires additional space for the stack, thus not strictly O(1) space complexity unless the stack is simulated in-place.

Here’s an example:

def are_parentheses_balanced(expr):
    stack = []
    for char in expr:
        if char in '([{':
            stack.append(char)
        elif char in ')]}':
            if not stack or not is_match(stack.pop(), char):
                return False
    return not stack

def is_match(opening, closing):
    pairs = {'(': ')', '[': ']', '{': '}'}
    return pairs[opening] == closing

print(are_parentheses_balanced('{(a+b)*[c/d]}'))

Output: True

This code uses a stack to push opening parentheses and pop them when a corresponding closing parenthesis is encountered. A helper function is_match() checks if the character popped from the stack matches the current closing parenthesis. The code returns a boolean indicating if the parentheses are balanced.

Method 4: Recursion with Two Pointers

Recursive two-pointer technique uses recursion to check for balanced parentheses in the given expression. It keeps track of indices for opening and closing parentheses and recursively validates sub-expressions. Due to using two pointers and recursing, it’s not strictly O(1) space, as recursive calls use stack space.

Here’s an example:

def is_balanced_recursive(expr, start, end):
    if start >= end:
        return True
    if expr[start] != '(' or expr[end] != ')':
        return False
    return is_balanced_recursive(expr, start + 1, end - 1)

expr = "((a+b)+(c+d))"
print(is_balanced_recursive(expr, 0, len(expr)-1))

Output: False

This recursive function checks if the first and last characters in the current substring are a pair of balanced parentheses and then calls itself with the next inner substring. This is a simplistic recursion approach that does not correctly handle all cases of nesting or different types of parentheses.

Bonus One-Liner Method 5: Using Generator Expressions

This Python one-liner uses generator expressions to create a clever but impractical solution. It uses Python’s ability to generate and consume tuples with accumulative counts. It is not truly O(1) space, as the generator stores state, and it’s not recommended for actual use but is a fun exercise in Python’s expressiveness.

Here’s an example:

expr = "(a+b)*(c+(d-e))"
balanced = not sum((1 for c in expr if c == '(') + (-1 for c in expr if c == ')'))
print(balanced)

Output: True

The one-liner uses generator expressions to create tuples where opening parentheses are counted as 1 and closing as -1. The sum of these tuples determines if the expression is balanced. This approach is succinct but obscures the underlying logic.

Summary/Discussion

  • Method 1: Brute Force Iterative Check. Simplest to implement. Easily understandable. Inefficient for large inputs.
  • Method 2: Counting Parentheses. Easy to follow. Inefficient for nested structures. Doesn’t handle different types of parentheses.
  • Method 3: Using Stack to Check for Nested Parentheses. Effective for nested and varied parentheses. Requires additional space. Not constant space complexity.
  • Method 4: Recursion with Two Pointers. Neat for small inputs. Does not handle all cases of nesting or parentheses types. Recursive implementation consumes stack space.
  • Bonus Method 5: Using Generator Expressions. Clever one-liner. Not practical for actual use. Obscures logic.