Topics › Strings › Hard String Problems
Minimum number of bracket reversals to make an expression balanced
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a string s that contains only the characters { and }. In one operation you may reverse a single bracket, turning a { into } or a } into {. Return the minimum number of reversals that make the expression balanced, or -1 if no number of reversals can balance it.
An expression is balanced when every opening bracket is closed later and pairs nest properly: {}, {{}} and {}{} are balanced, while }{ and {{} are not. A handy equivalent: scan left to right with a counter that goes up on { and down on }. The string is balanced exactly when the counter never drops below zero and finishes at zero.
Constraints: 0 ≤ n ≤ 10^5. The empty string is balanced and needs 0 reversals.
Example 1
s = "}{{}}{{{"
answer = 3 // reverse positions 0, 5 and 7: "{{{}}}{}"
Example 2
s = "{{}{{{}{{}}{{"
answer = -1 // 13 characters: an odd count can never pair up
Example 3
s = "}{"
answer = 2 // both brackets face the wrong way: "{}"
Two facts that drive every approach
Odd length is hopeless. Reversals never change the length, and a balanced string has exactly as many { as }, so its length is even. If n is odd, return -1 immediately. If n is even, an answer always exists: for example, rewriting the string as {}{}...{} uses at most n reversals.
Matched pairs can be ignored. Any { that is already closed by a later } can be cancelled together with it, exactly as a stack checker for parentheses does. What remains after all cancellations cannot contain {} anywhere, so it must look like }}}...{{{: some c unmatched closing brackets followed by some o unmatched opening brackets. Because n is even and every cancellation removes two characters, c + o is even, so c and o are both even or both odd.
For the leftover }}}...{{{ the cheapest repair is clear. Reverse every other } so that }} becomes {}, and reverse every other { so that {{ becomes {}. If c and o are both odd, one }{ remains in the middle and both of its brackets need reversing. That gives
answer = ceil(c / 2) + ceil(o / 2) = (c + 1) / 2 + (o + 1) / 2 (integer division)
The approaches below differ only in how they find c and o, plus one exhaustive method that confirms the formula.
Approach 1: Brute force
Intuition. Each bracket is either reversed or left alone, so there are 2^n possible sets of reversals. Try every set, check whether the resulting string is balanced with the counter test, and keep the smallest set that works. It is far too slow for real inputs, but it makes no assumptions at all, which is exactly what we want from a reference to test the clever methods against.
Algorithm.
- If
nis odd, return-1. - For every bit mask from
0to2^n - 1, reverse the characters whose bits are set. - Run the counter check. If the string is balanced and the mask has fewer set bits than the best so far, record it.
- Return the best count.
static int bruteForce(String s) {
int n = s.length();
if (n % 2 == 1) return -1;
if (n > 20) throw new IllegalArgumentException("brute force is for tiny inputs only");
int best = Integer.MAX_VALUE;
for (int mask = 0; mask < (1 << n); mask++) {
int flips = Integer.bitCount(mask);
if (flips >= best) continue;
int balance = 0;
boolean ok = true;
for (int i = 0; i < n && ok; i++) {
char c = s.charAt(i);
if ((mask >> i & 1) == 1) c = (c == '{') ? '}' : '{';
balance += (c == '{') ? 1 : -1;
if (balance < 0) ok = false;
}
if (ok && balance == 0) best = flips;
}
return best;
}
Complexity. Time O(2^n · n): every mask is checked in linear time. Space O(1) beyond the input. The method refuses inputs longer than 20 characters; the Java file uses it to verify the other two methods on every bracket string of length up to 10.
Approach 2: Better, cancel pairs with a stack
Intuition. Implement the second fact literally. Push each character; when a } arrives and the top of the stack is a {, they form a matched pair, so pop instead of pushing. Whatever is left on the stack is the }}}...{{{ leftover, and counting its two kinds gives c and o.
Algorithm.
- If
nis odd, return-1. - For each character: if it is
}and the stack top is{, pop; otherwise push it. - Count the
{and}left on the stack. - Return
(open + 1) / 2 + (close + 1) / 2.
static int withStack(String s) {
if (s.length() % 2 == 1) return -1;
Deque<Character> st = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '}' && !st.isEmpty() && st.peek() == '{') st.pop(); // cancel a matched pair
else st.push(c);
}
int open = 0, close = 0; // the leftover looks like }}}...{{{
for (char c : st) {
if (c == '{') open++;
else close++;
}
return (open + 1) / 2 + (close + 1) / 2;
}
Complexity. Time O(n): each character is pushed and popped at most once. Space O(n) for the stack in the worst case, for example a string of only {.
Approach 3: Optimal, two counters
Intuition. The stack only ever holds } characters below { characters, and we never look deeper than the top. So it is enough to know how many unmatched { are waiting (open) and how many } could not be matched (close). A } either closes a waiting { (decrement open) or, if none is waiting, becomes permanently unmatched (increment close), because no later { can ever close a bracket that came before it.
Algorithm.
- If
nis odd, return-1. - Set
open = 0andclose = 0. - For each character: on
{doopen++; on}doopen--ifopen > 0, otherwiseclose++. - Return
(open + 1) / 2 + (close + 1) / 2.
static int optimal(String s) {
if (s.length() % 2 == 1) return -1;
int open = 0, close = 0; // unmatched '{' and unmatched '}' seen so far
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '{') open++;
else if (open > 0) open--; // this '}' closes an earlier '{'
else close++; // nothing to close: an unmatched '}'
}
return (open + 1) / 2 + (close + 1) / 2;
}
Dry run on s = "}{{}}{{{":
| i | char | open | close | note |
|---|---|---|---|---|
| 0 | } |
0 | 1 | nothing to close |
| 1 | { |
1 | 1 | |
| 2 | { |
2 | 1 | |
| 3 | } |
1 | 1 | closes position 2 |
| 4 | } |
0 | 1 | closes position 1 |
| 5 | { |
1 | 1 | |
| 6 | { |
2 | 1 | |
| 7 | { |
3 | 1 |
The answer is (3 + 1) / 2 + (1 + 1) / 2 = 2 + 1 = 3, matching Example 1.
Complexity. Time O(n), one pass. Space O(1), two integers.
Why the formula cannot be beaten
Achieving ceil(c / 2) + ceil(o / 2) was shown above. Here is why no solution can use fewer reversals, even one that also touches already-matched brackets.
Track the running balance (+1 for {, -1 for }). With the counter method, the balance equals open - close, and it reaches a new minimum exactly when close increases, so its lowest value is -c. Let P be the first position where the balance is -c.
- Before
P. In a balanced result, the balance atPmust be at least0. One reversal changes it by at most2, so at leastceil(c / 2)reversals must happen at or beforeP. - After
P. The total balance of the string iso - c, so fromPto the end the balance rises byo. In the final string it must fall from something at least0to exactly0. Each reversal afterPlowers that rise by at most2, so at leastceil(o / 2)reversals must happen afterP.
The two groups are disjoint, so every valid solution uses at least ceil(c / 2) + ceil(o / 2) reversals.
Edge cases and pitfalls
- Odd length. Check it first; otherwise the formula returns a number for an impossible input.
- Empty string. Already balanced:
0. All three methods handle it without a special case. - Rounding.
c / 2 + o / 2is wrong when both are odd:}{would give0instead of2. Use(x + 1) / 2. - Greedy matching is safe. Matching each
}with the most recent waiting{never hurts; whichever{it closes, the countscandocome out the same. - Characters other than brackets. The problem guarantees only
{and}. If other characters may appear, skip them explicitly; the counter method as written treats anything that is not{as a closing bracket.
Related problems
- Balanced Paranthesis is the yes/no version, solved with the same stack or counter.
- Maximum Nesting Depth of the Parentheses tracks the same running balance and reports its maximum.
- Remove Outermost Parentheses uses the balance counter to find primitive balanced blocks.
- Generate Parentheses builds every balanced string, enforcing the same “never below zero” rule.