Placement Prep

Remove Brackets from Algebraic Expressions: C, Java, Python

Stack-based sign-aware algorithm to remove brackets from algebraic expressions. Covers C, C++, Java, Python with hand-traced examples and edge cases.

By FACE Prep Team 6 min read
string-manipulation stack java python c-programming interview-questions data-structures

Removing brackets from an algebraic expression is straightforward when every bracket is preceded by a plus sign, but requires sign propagation when a minus sign precedes an opening bracket.

Given a-(b+c), the correct output is a-b-c, not a-b+c. The bracket preceded by - flips every sign inside. That one rule is the entire difficulty of this problem.

Why Removing Brackets Is Not Trivial

The naive approach is to replace every ( and ) with an empty string:

expr.replace('(', '').replace(')', '')

This works for (a+b)+c giving a+b+c, where no minus precedes any bracket. It fails the moment a minus does:

  • Input: a-(b+c)
  • Naive output: a-b+c (wrong)
  • Correct output: a-b-c

The minus before (b+c) turns the entire interior negative. b was positive inside the bracket, so it becomes -b outside. c was positive inside, so it becomes -c outside. The bracket plus its preceding minus effectively distributes a negation across every term it contains.

This distribution applies recursively. In -((a-b)-(c+d)), there are two levels of negation to track. The correct output is -a+b+c+d, which the naive approach cannot produce.

Cryptarithmetic problems similarly demand careful sign and carry accounting across positions; the discipline of tracing state through each character transfers directly to this problem.

The Stack-Based Sign-Aware Algorithm

The algorithm maintains two pieces of state:

  • sign_stack: a stack of integers, each +1 or -1, recording the effective sign multiplier at the current nesting depth.
  • current_sign: the sign of the next variable, set to +1 when a + is read and -1 when a - is read.

The rules for each character type:

  • +: set current_sign = +1
  • -: set current_sign = -1
  • (: push sign_stack.top() * current_sign onto the stack; reset current_sign to +1
  • ): pop the stack
  • variable (letter): compute effective = sign_stack.top() * current_sign; append the sign and the letter to the result

When ( is encountered, multiplying the top of the stack by current_sign captures the propagated direction. If current_sign is -1 and the current depth was +1, the new depth is -1 (everything inside is negated). If both are -1, the new depth is +1 (a double negation cancels out).

The sign stack starts with a single +1 entry. That entry acts as the ground-level multiplier. Every bracket opens a new level whose multiplier is derived from the level above it, chained through the sign that preceded the bracket.

The data structures programs guide in C, C++, Java, and Python covers the mechanics of stack implementation across languages if you need a refresher before writing this code. The Wikipedia article on order of operations explains why bracket removal must respect sign context, not just syntactic nesting.

Code in C, C++, Java, and Python

Python

def remove_brackets(expr):
    result = []
    sign_stack = [1]   # base multiplier at current depth
    current_sign = 1   # sign of the next variable

    for ch in expr:
        if ch == '+':
            current_sign = 1
        elif ch == '-':
            current_sign = -1
        elif ch == '(':
            sign_stack.append(sign_stack[-1] * current_sign)
            current_sign = 1
        elif ch == ')':
            sign_stack.pop()
        else:
            effective = sign_stack[-1] * current_sign
            if result:
                result.append('+' if effective > 0 else '-')
            elif effective < 0:
                result.append('-')
            result.append(ch)

    return ''.join(result)

# Test cases
print(remove_brackets("((a+b)+c)"))         # a+b+c
print(remove_brackets("a-(b+c)"))           # a-b-c
print(remove_brackets("(a+b)+(c-d)"))       # a+b+c-d
print(remove_brackets("-((a-b)-(c+d))"))    # -a+b+c+d

Java

import java.util.Deque;
import java.util.ArrayDeque;

public class RemoveBrackets {

    public static String removeBrackets(String expr) {
        StringBuilder result = new StringBuilder();
        Deque<Integer> signStack = new ArrayDeque<>();
        signStack.push(1);
        int currentSign = 1;

        for (char ch : expr.toCharArray()) {
            if (ch == '+') {
                currentSign = 1;
            } else if (ch == '-') {
                currentSign = -1;
            } else if (ch == '(') {
                signStack.push(signStack.peek() * currentSign);
                currentSign = 1;
            } else if (ch == ')') {
                signStack.pop();
            } else {
                int effective = signStack.peek() * currentSign;
                if (result.length() > 0) {
                    result.append(effective > 0 ? '+' : '-');
                } else if (effective < 0) {
                    result.append('-');
                }
                result.append(ch);
            }
        }
        return result.toString();
    }

    public static void main(String[] args) {
        System.out.println(removeBrackets("((a+b)+c)"));        // a+b+c
        System.out.println(removeBrackets("a-(b+c)"));          // a-b-c
        System.out.println(removeBrackets("(a+b)+(c-d)"));      // a+b+c-d
        System.out.println(removeBrackets("-((a-b)-(c+d))"));   // -a+b+c+d
    }
}

C++

#include <iostream>
#include <string>
#include <stack>

std::string removeBrackets(const std::string& expr) {
    std::string result;
    std::stack<int> signStack;
    signStack.push(1);
    int currentSign = 1;

    for (char ch : expr) {
        if (ch == '+') {
            currentSign = 1;
        } else if (ch == '-') {
            currentSign = -1;
        } else if (ch == '(') {
            signStack.push(signStack.top() * currentSign);
            currentSign = 1;
        } else if (ch == ')') {
            signStack.pop();
        } else {
            int effective = signStack.top() * currentSign;
            if (!result.empty()) {
                result += (effective > 0) ? '+' : '-';
            } else if (effective < 0) {
                result += '-';
            }
            result += ch;
        }
    }
    return result;
}

int main() {
    std::cout << removeBrackets("((a+b)+c)")       << "\n"; // a+b+c
    std::cout << removeBrackets("a-(b+c)")          << "\n"; // a-b-c
    std::cout << removeBrackets("(a+b)+(c-d)")      << "\n"; // a+b+c-d
    std::cout << removeBrackets("-((a-b)-(c+d))")   << "\n"; // -a+b+c+d
    return 0;
}

C

#include <stdio.h>
#include <string.h>

void remove_brackets(const char *expr, char *result) {
    int sign_stack[256];
    int top = 0;
    sign_stack[top] = 1;

    int current_sign = 1;
    int len = 0;

    for (int i = 0; expr[i] != '\0'; i++) {
        char ch = expr[i];
        if (ch == '+') {
            current_sign = 1;
        } else if (ch == '-') {
            current_sign = -1;
        } else if (ch == '(') {
            top++;
            sign_stack[top] = sign_stack[top - 1] * current_sign;
            current_sign = 1;
        } else if (ch == ')') {
            top--;
        } else {
            int effective = sign_stack[top] * current_sign;
            if (len > 0) {
                result[len++] = (effective > 0) ? '+' : '-';
            } else if (effective < 0) {
                result[len++] = '-';
            }
            result[len++] = ch;
        }
    }
    result[len] = '\0';
}

int main(void) {
    char out[256];
    remove_brackets("((a+b)+c)", out);       printf("%s\n", out); // a+b+c
    remove_brackets("a-(b+c)", out);          printf("%s\n", out); // a-b-c
    remove_brackets("(a+b)+(c-d)", out);      printf("%s\n", out); // a+b+c-d
    remove_brackets("-((a-b)-(c+d))", out);   printf("%s\n", out); // -a+b+c+d
    return 0;
}

Hand-Traced Examples

Trace 1: (a+b)+(c-d) gives a+b+c-d

State at each step (stack shown as [...], top at right):

  • ( — push stack[-1] * 1 = 1. Stack: [1, 1]
  • a — effective 1 * 1 = 1. Output: a
  • + — current_sign = 1
  • b — effective 1 * 1 = 1. Output: a+b
  • ) — pop. Stack: [1]
  • + — current_sign = 1
  • ( — push 1 * 1 = 1. Stack: [1, 1]
  • c — effective 1 * 1 = 1. Output: a+b+c
  • - — current_sign = -1
  • d — effective 1 * -1 = -1. Output: a+b+c-d
  • ) — pop. Stack: [1]

Result: a+b+c-d

Trace 2: -((a-b)-(c+d)) gives -a+b+c+d

  • - — current_sign = -1
  • ( — push 1 * -1 = -1. Stack: [1, -1]
  • ( — push -1 * 1 = -1. Stack: [1, -1, -1]
  • a — effective -1 * 1 = -1. Output: -a
  • - — current_sign = -1
  • b — effective -1 * -1 = 1. Output: -a+b
  • ) — pop. Stack: [1, -1]
  • - — current_sign = -1
  • ( — push -1 * -1 = 1. Stack: [1, -1, 1]
  • c — effective 1 * 1 = 1. Output: -a+b+c
  • + — current_sign = 1
  • d — effective 1 * 1 = 1. Output: -a+b+c+d
  • ) — pop. Stack: [1, -1]
  • ) — pop. Stack: [1]

Result: -a+b+c+d

The second trace shows why two negations cancel: the outer -( flips to depth -1, and the inner -( at that depth produces -1 * -1 = 1, effectively restoring positivity for the terms in the second group.

Edge Cases and Complexity

Three edge cases worth handling explicitly:

  • Empty string: the loop body never executes; the result is an empty string.
  • No brackets: sign_stack stays at [1]; current_sign flips on each operator. The output matches the input.
  • Leading minus with no bracket: in -a+b, the first character sets current_sign = -1, so a outputs as -a. Correct.

The Directi technical interview guide covers stack-based problems that appear in product-company coding rounds, including expression evaluation variants that build on this foundation. Working through this problem first reduces the cognitive load of the more complex variants.

Complexity

  • Time: O(n). One left-to-right pass; each character is processed in constant time.
  • Space: O(n) for the output string and the stack. Stack depth is at most the bracket nesting depth, which is bounded by n/2.

The algorithm does not require a second pass, backtracking, or look-ahead. It processes the string character by character in one direction, updating a small amount of state per step. That clean O(n) profile is why this approach is preferred over recursive descent for interview settings, where implementation speed and correctness under time pressure both matter.


If you built this solution and want to apply the same pattern to real tools (expression parsers, calculators, small language interpreters), TinkerLLM gives you a hands-on environment to prototype those ideas at ₹299. The sign-propagation logic here is the same mental model used in building simple evaluators; the platform is the place to turn that into a working project rather than a solved exercise.

Primary sources

Frequently asked questions

What happens when a minus sign precedes an opening bracket?

Every term inside that bracket has its sign flipped. A plus becomes a minus and a minus becomes a plus. The stack-based algorithm handles this by multiplying the current sign by the base sign before pushing it onto the stack.

What is the time complexity of the stack-based bracket removal algorithm?

O(n) time and O(n) space, where n is the length of the input string. The algorithm makes a single left-to-right pass, and the stack depth is bounded by the nesting depth.

Does this algorithm handle deeply nested brackets like ((a-(b+c)))?

Yes. The stack grows by one entry for each opening bracket and shrinks by one for each closing bracket, so any nesting depth is handled correctly as long as brackets are balanced.

Why does simply stripping brackets produce the wrong answer?

Stripping brackets ignores sign context. For a-(b+c), stripping brackets gives a-b+c, but the correct answer is a-b-c because the minus before the bracket negates all terms inside.

What is the output for (a+b)+(c-d)?

a+b+c-d. The first bracket is preceded by the implicit plus at the start, so signs inside are unchanged. The second bracket is also preceded by plus, so signs are unchanged. Result: a+b+c-d.

What should the algorithm output for an expression with no brackets?

The expression unchanged. When no brackets are encountered, the stack never grows beyond its initial entry, and each variable is output with its sign as written.

Which companies ask this problem in campus interviews?

Directi, Dream11, and similar product-focused companies ask expression-parsing questions in their technical rounds. It appears as a string manipulation or stack question in coding assessments.

Build AI projects

A self-paced playground for building with LLMs.

TinkerLLM is FACE Prep's sister property. A guided environment for shipping real LLM applications, the kind of project that earns a paragraph on your resume, not a line.

Try TinkerLLM (₹299 launch)
Free AI Roadmap PDF