STACK › PARENTHESES PROBLEM
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →The expression's value is determined by its binary tree: every operator combines a left subtree and a right subtree. Parentheses in the input tell you which combinations must happen first, while precedence tells you which combinations would happen first even without parentheses. The task is therefore easier in two stages: recover that tree, then choose the smallest parentheses set that still prints the same tree.
When an operator is printed inside another operator, compare their precedences. A lower-precedence child must be wrapped, because the parent would otherwise execute before part of that child. A higher-precedence child is safe. Equal precedence is safe on the left because left-to-right evaluation already groups it correctly, but the right side needs care: subtraction and division change meaning when their right operand is regrouped.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | Each input character is scanned once, each operator is reduced once, and each tree node is rendered once. Stack operations are constant-time, so no character or node causes a second traversal. |
| Space | O(n) extra | The output string is required output and is excluded. The node array, value and operator stacks, and rendering work stack can each contain linearly many entries; deeply nested or long flat expressions reach this bound. |
#include <string>
#include <vector>
using namespace std;
class Solution {
struct Node {
char op;
char value;
int left;
int right;
Node(char v) : op(0), value(v), left(-1), right(-1) {}
Node(char o, int l, int r) : op(o), value(0), left(l), right(r) {}
};
vector<Node> nodes;
int precedence(char c) {
return (c == '+' || c == '-') ? 1 : 2;
}
void reduceTop(vector<int>& values, vector<char>& operators) {
char op = operators.back();
operators.pop_back();
int right = values.back();
values.pop_back();
int left = values.back();
values.pop_back();
nodes.push_back(Node(op, left, right));
values.push_back(static_cast<int>(nodes.size()) - 1);
}
bool needsParentheses(char parent, char child, bool isRightChild) {
if (parent == 0 || child == 0) return false;
int parentPrecedence = precedence(parent);
int childPrecedence = precedence(child);
if (childPrecedence < parentPrecedence) return true;
if (childPrecedence > parentPrecedence) return false;
if (!isRightChild) return false;
return parent != '+' && parent != '*';
}
public:
string removeBrackets(string Exp) {
nodes.clear();
vector<int> values;
vector<char> operators;
for (char c : Exp) {
if (c >= 'A' && c <= 'Z') {
nodes.push_back(Node(c));
values.push_back(static_cast<int>(nodes.size()) - 1);
} else if (c == '(') {
operators.push_back(c);
} else if (c == ')') {
while (!operators.empty() && operators.back() != '(') {
reduceTop(values, operators);
}
operators.pop_back();
} else {
while (!operators.empty() && operators.back() != '(' &&
precedence(operators.back()) >= precedence(c)) {
reduceTop(values, operators);
}
operators.push_back(c);
}
}
while (!operators.empty()) {
reduceTop(values, operators);
}
struct Task {
int kind;
int nodeId;
char ch;
char parent;
bool isRightChild;
};
string answer;
answer.reserve(Exp.size());
vector<Task> work;
work.push_back({0, values.back(), 0, 0, false});
while (!work.empty()) {
Task task = work.back();
work.pop_back();
if (task.kind == 1) {
answer.push_back(task.ch);
continue;
}
Node& current = nodes[task.nodeId];
if (current.op == 0) {
answer.push_back(current.value);
continue;
}
bool wrap = needsParentheses(
task.parent, current.op, task.isRightChild
);
if (wrap) {
work.push_back({1, 0, ')', 0, false});
}
work.push_back({0, current.right, 0, current.op, true});
work.push_back({1, 0, current.op, 0, false});
work.push_back({0, current.left, 0, current.op, false});
if (wrap) {
work.push_back({1, 0, '(', 0, false});
}
}
return answer;
}
};The parser is a shunting-yard construction, but its values are node indices instead of numbers. Every reduction removes one operator and its two child indices, creates one parent node, and pushes that parent back as a complete value. This preserves the original grouping in a compact tree. The opening parenthesis is only a barrier during parsing; it never becomes a node and therefore can never appear in the result by accident.
The renderer receives the parent operator and the child's side as context. For a lower-precedence child, omitting parentheses lets the parent bind too early. For an equal-precedence right child, omission changes the grouping for subtraction or division. The two exceptions, addition and multiplication, are safe here because their equal-precedence right combinations can be flattened without changing the arithmetic expression's value.