Topics › Strings › Medium String Problems
Maximum Nesting Depth of the Parentheses
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
A string is a valid parentheses string (VPS) if it is empty, or it is a single character that is not a bracket, or it has the form (A) where A is a VPS, or it is the concatenation AB of two VPS. The nesting depth is defined along the same lines: the empty string and a lone non-bracket character have depth 0, (A) has depth 1 + depth(A), and AB has depth max(depth(A), depth(B)).
You are given a VPS s built from digits, the operators + - * / and round brackets. Return its nesting depth. In plain words: what is the largest number of brackets that are open at the same time while reading s from left to right?
Constraints: 1 ≤ |s| ≤ 100; s is guaranteed to be a valid parentheses string. The Java file also accepts the empty string.
Example 1
s = "(1+(2*3)+((8)/4))+1"
answer = 3 // the digit 8 sits inside three brackets
Example 2
s = "(1)+((2))+(((3)))"
answer = 3 // the last group opens three brackets in a row
Example 3
s = "1+(2*3)/(2-1)"
answer = 1 // two bracket groups, but they are siblings, never nested
Approach 1: Brute force, recount the open brackets at every ‘(‘
Intuition. Pick any position i that holds an opening bracket. How deeply nested is it? Exactly as many brackets are open at that moment as there are ( in s[0..i] minus the ) in s[0..i]: every ) closes exactly one earlier (, and because the string is valid no ) is ever left without a partner. So the depth of the bracket at i is the balance of the prefix ending at i, and the depth of the whole string is the largest such balance over all opening brackets. The brute force simply recomputes that balance from scratch for every (.
Why only at opening brackets? The depth can only reach a new maximum at the instant a bracket opens; between an opening bracket and the next one the balance can only stay the same or fall. So checking at every ( is enough, and checking elsewhere is redundant.
Algorithm.
- Set
best = 0. - For every index
iwheres[i] == '(': - Walk
jfrom0toi, adding one for each(and subtracting one for each); call the resultopen. - Set
best = max(best, open). - Return
best.
static int bruteForce(String s) {
int n = s.length(), best = 0;
for (int i = 0; i < n; i++) {
if (s.charAt(i) != '(') continue;
int open = 0; // unmatched '(' in s[0..i], including this one
for (int j = 0; j <= i; j++) {
char c = s.charAt(j);
if (c == '(') open++;
else if (c == ')') open--;
}
best = Math.max(best, open);
}
return best;
}
Complexity. Time O(n^2): each of up to n opening brackets triggers a scan of up to n characters. Space O(1): two counters.
The repeated rescans are pure waste: the balance at position i differs from the balance at position i - 1 by at most one, so the prefix never needs to be read again.
Approach 2: Better, an explicit stack
Intuition. Brackets match in last-opened, first-closed order, which is exactly what a stack models. Push every (, pop on every ). Right after a push the stack holds one entry for every bracket that currently encloses us, so its size at that instant is the nesting depth of the bracket just pushed. The answer is the tallest the stack ever becomes.
This is the general-purpose tool for bracket problems. When the input may be invalid, or several bracket types have to be matched against each other, the stack is genuinely needed because we must know which bracket is on top. Here, as the next approach shows, we never look at the top at all.
Algorithm.
- Create an empty stack and set
best = 0. - For each character
cofs:- If
c == '(', push it and setbest = max(best, stack.size()). - If
c == ')', pop one entry. - Otherwise ignore the character.
- If
- Return
best.
static int better(String s) {
Deque<Character> stack = new ArrayDeque<>();
int best = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(') {
stack.push(c);
best = Math.max(best, stack.size());
} else if (c == ')') {
stack.pop();
}
}
return best;
}
Complexity. Time O(n): one push or pop per bracket, each O(1) on an ArrayDeque. Space O(n): in a string like ((((...)))) the stack grows to n / 2 entries.
Pushing on a stack and never reading what was pushed is a sign that the stack is carrying more information than we use.
Approach 3: Optimal, replace the stack with a counter
Intuition. The stack in Approach 2 only ever contains the character (. We never inspect its top, we only ask for its size. A stack whose contents are irrelevant is just an integer: increment on (, decrement on ). The running value is the current depth and the maximum over the scan is the answer. The validity guarantee means the counter never drops below zero and ends at zero.
Algorithm.
- Set
depth = 0andbest = 0. - For each character
cofs:- If
c == '(', dodepth++and, ifdepth > best, setbest = depth. - If
c == ')', dodepth--.
- If
- Return
best.
static int optimal(String s) {
int depth = 0, best = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(') {
depth++;
if (depth > best) best = depth;
} else if (c == ')') {
depth--;
}
}
return best;
}
Dry run on s = "(1+(2*3)+((8)/4))+1", showing only the bracket positions:
| i | s[i] | depth after | best |
|---|---|---|---|
| 0 | ( | 1 | 1 |
| 3 | ( | 2 | 2 |
| 7 | ) | 1 | 2 |
| 9 | ( | 2 | 2 |
| 10 | ( | 3 | 3 |
| 12 | ) | 2 | 3 |
| 15 | ) | 1 | 3 |
| 16 | ) | 0 | 3 |
The counter ends at 0, as it must for a valid string, and the peak of 3 was reached at the bracket directly enclosing the 8.
Complexity. Time O(n): a single pass with constant work per character. Space O(1): two integers.
Only the increments can produce a new maximum, so best is updated inside the ( branch alone. Checking after a decrement is harmless but can never change the answer.
Edge cases and pitfalls
- No brackets at all.
"1"and"1+2*3"have depth0. The counter never moves andbeststays0. - Empty string. Also depth
0. Both loops simply do not execute. - Siblings do not stack.
"()()()"has depth1, not3. Each)brings the counter back to0before the next(. - Counting brackets is not the answer.
"(1)+((2))"contains three(but its depth is2. Depth is about simultaneity, not totals. - Input that is not valid. If the guarantee is dropped, the counter can go negative (
")(") or end non-zero ("(("). Detect that with a check fordepth < 0during the scan anddepth != 0at the end, and treat the string as invalid. Approach 2 would throw on an empty-stack pop, which is why the counter version is also the easier one to harden. - Other characters. Digits and operators must be skipped, not treated as closing brackets. Both branches test the exact character, so anything else falls through.
Related problems
- Remove Outermost Parentheses uses the same counter to recognise the moments when the depth crosses between
0and1. - Balanced Paranthesis is the version where validity is not guaranteed and several bracket types appear, so the real stack of Approach 2 becomes necessary.
- Generate Parentheses builds every valid parentheses string of a given length by tracking the same open and close counts.
- Minimum number of bracket reversals to make an expression balanced counts the unmatched brackets that the same scan leaves behind.