Balanced brackets
A string s contains only the characters ( ) [ ] { }. Return
true if the brackets are balanced: every opening bracket is closed by a bracket of the
same type, and brackets close in the reverse order to the one in which they opened. Otherwise
return false.
Examples
| Input | Output | Why |
|---|---|---|
s = "([]{})" | true | Each pair closes inside the pair that contains it. |
s = "([)]" | false | The counts match, but ] arrives while ( is still the innermost open bracket. |
s = "((" | false | Two brackets are left open at the end. |
Constraints
- 1 ≤ s.length ≤ 10⁴
- s contains only ( ) [ ] { }
Hints
Hint 1
Counting each type separately is not enough: "([)]" has one of each and is still wrong. What does a closing bracket actually need to match?
Hint 2
A closing bracket must match the most recently opened bracket that has not been closed yet. Which data structure hands you "the most recent" in O(1)?
Hint 3
Push openers. On a closer, the stack must be non-empty and its top must be the matching opener; pop it. At the end the stack must be empty.
Dry run on "{[()]}("
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 8s{0i[1(2)3]4}5(6stack{0open1
'{' opens a bracket. Push it; it waits for its partner.
-
Step 2 / 8s{0[1i(2)3]4}5(6stack{0[1open2
'[' opens a bracket. Push it; it waits for its partner.
-
Step 3 / 8s{0[1(2i)3]4}5(6stack{0[1(2open3
'(' opens a bracket. Push it; it waits for its partner.
-
Step 4 / 8s{0[1(2)3i]4}5(6stack{0[1open2
')' closes '(', which is on top of the stack. Pop it.
-
Step 5 / 8s{0[1(2)3]4i}5(6stack{0open1
']' closes '[', which is on top of the stack. Pop it.
-
Step 6 / 8s{0[1(2)3]4}5i(6stackempty0open0
'}' closes '{', which is on top of the stack. Pop it.
-
Step 7 / 8s{0[1(2)3]4}5(6istack(0open1
'(' opens a bracket. Push it; it waits for its partner.
-
Step 8 / 8stack(0answerfalse
The string is finished but 1 bracket is still open. Unbalanced.
How to think about it
Read the string left to right and keep the brackets that are open but not yet closed. A closing bracket can only ever close the innermost of them, the one opened most recently. Brackets are closed in the reverse of the order they were opened, which is last in, first out, and that is exactly what a stack does.
- An opening bracket goes on the stack.
- A closing bracket needs the top of the stack to be its partner. If the stack is empty, or the top is a different type, the string is unbalanced and you can stop at once. Otherwise pop.
- At the end, anything still on the stack was never closed.
Counters fail on ([)] because they record how many brackets
are open, but not in what order. The stack records the order, and the order is what this problem
is about.
Complexity. Time O(n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def is_balanced(s):
need = {')': '(', ']': '[', '}': '{'} # each closer -> the opener it must match
stack = []
for ch in s:
if ch in need:
if not stack or stack.pop() != need[ch]:
return False # nothing open, or the wrong thing open
else:
stack.append(ch) # an opener waits for its partner
return not stack # anything left was never closed#include <string>
#include <vector>
bool solve(const std::string& s) {
std::vector<char> stack;
for (char ch : s) {
if (ch == '(' || ch == '[' || ch == '{') {
stack.push_back(ch); // an opener waits for its partner
continue;
}
const char want = ch == ')' ? '(' : ch == ']' ? '[' : '{';
if (stack.empty() || stack.back() != want) return false;
stack.pop_back();
}
return stack.empty(); // anything left was never closed
}class Solution {
public boolean isBalanced(String s) {
char[] stack = new char[s.length()];
int top = 0;
for (char ch : s.toCharArray()) {
if (ch == '(' || ch == '[' || ch == '{') {
stack[top++] = ch; // an opener waits for its partner
continue;
}
char want = ch == ')' ? '(' : ch == ']' ? '[' : '{';
if (top == 0 || stack[top - 1] != want) return false;
top--;
}
return top == 0; // anything left was never closed
}
}function isBalanced(s) {
const need = { ')': '(', ']': '[', '}': '{' }; // closer -> opener it must match
const stack = [];
for (const ch of s) {
if (ch in need) {
if (stack.length === 0 || stack.pop() !== need[ch]) return false;
} else {
stack.push(ch); // an opener waits for its partner
}
}
return stack.length === 0; // anything left was never closed
}fn solve(s: &str) -> bool {
let mut stack: Vec<char> = Vec::new();
for ch in s.chars() {
let want = match ch {
')' => '(',
']' => '[',
'}' => '{',
_ => {
stack.push(ch); // an opener waits for its partner
continue;
}
};
if stack.pop() != Some(want) {
return false; // nothing open, or the wrong thing open
}
}
stack.is_empty() // anything left was never closed
}-- The stack is a list with its top at the head.
solve :: String -> Bool
solve = go []
where
go stack [] = null stack -- anything left was never closed
go stack (c : cs)
| c `elem` "([{" = go (c : stack) cs -- an opener waits for its partner
| otherwise = case stack of
(top : rest) | top == opener c -> go rest cs
_ -> False
opener ')' = '('
opener ']' = '['
opener _ = '{'
Where people lose marks
- Using one counter per bracket type. It accepts "([)]", where the counts are fine and the nesting is not.
- Forgetting the empty-stack check on a closing bracket. ")" pops from an empty stack, which crashes in some languages and silently returns undefined in JavaScript.
- Returning true as soon as the loop ends, without checking that the stack is empty. "((" would pass.
- Mapping opener to closer and then looking up the wrong direction. Map each closer to the opener it needs; that is the lookup you do.
Variants to try
- Return the index of the first bracket that breaks the balance, or −1: the loop already knows it when it returns false.
- Only one bracket type: the stack collapses to a counter, and O(1) space is enough.
- Find the minimum number of insertions needed to balance the string: count unmatched closers during the scan and add what is left on the stack.
- Longest balanced substring: push indices instead of characters, and measure from the index below each match.
Next problems
Days until a warmer day
For each day, how long until it is warmer? Days wait on a stack until a warmer one arrives and settles them.
Open the problem → Hard25 minLargest rectangle in a bar chart
Each bar is the height of some rectangle. A stack finds, for every bar at once, how far it can stretch.
Open the problem →Write a solution and run it against the real test table.
Keep going
Days until a warmer day
For each day, how long until it is warmer? Days wait on a stack until a warmer one arrives and settles them.
Open the problem → Hard25 minLargest rectangle in a bar chart
Each bar is the height of some rectangle. A stack finds, for every bar at once, how far it can stretch.
Open the problem → StackThe pattern behind it
Keep the elements whose question is still open; each new element settles the ones it beats and waits its turn.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.