Score of Parentheses — Stack Depth Doubling and O(1) Space
Advertisement
Problem Statement
Given a balanced parentheses string s, return the score of the string based on the following rule:
"()"has score 1.ABhas scoreA + B, whereAandBare balanced parentheses strings.(A)has score2 * A, whereAis a balanced parentheses string.
Constraints:
2 <= s.length <= 50sconsists of(and)only.sis a balanced parentheses string.
Input: s = "()"
Output: 1Input: s = "(())"
Output: 2
Explanation: (()) = 2 * () = 2 * 1 = 2Input: s = "(()(()))"
Output: 6
Explanation: (()) = 2, () = 1, together = 3, so ((3)) = 6... actually:
(()(()))
= (()) + (()) is wrong, it's one big group
inner: () + (()) = 1 + 2 = 3, outer: (3) = 6Why This Problem Matters
LC 856 is a deceptively deep problem. The surface-level solution uses a stack (O(n) space), but the optimal solution uses a bit-shift depth trick that achieves O(1) space — and understanding why it works demonstrates real mathematical depth.
This problem tests: recursive nesting with a stack (same pattern as Decode String, LC 394), optimization from O(n) to O(1) space, and the insight that every () contributes 2^depth to the total score.
Companies: Google, Amazon. Often asked as a follow-up to Decode String (LC 394) or Valid Parentheses (LC 20) to test whether you can derive the mathematical shortcut.
The Core Insight
Stack approach: Use a stack of running scores at each nesting level. On (, push 0 (start a new scope). On ), pop the scope's score v and add max(1, 2*v) to the parent scope: if v == 0 the scope contained () → score 1; otherwise it contained (A) → score 2*A.
Depth doubling insight (O(1) space): Every () at depth d contributes 2^d to the final score. Proof by induction: at depth 0, () = 1 = 2^0. At depth 1, (()) = 2*(()) = 2*1 = 2 = 2^1. At depth 2, ((())) = 4 = 2^2. Nested (A) simply adds one to the depth.
So: scan the string, tracking depth. Every time we see () (current char is ) and previous char was (), add 2^depth (or 1 << depth with bit shift) to the answer — where depth is the current depth before the closing paren.
This is a beautiful O(1) space solution that emerges from understanding the problem's mathematical structure.
Visual Dry Run
Input: s = "(()(()))"
Stack approach:
| Char | Action | Stack |
|---|---|---|
| '(' | push 0 | [0, 0] |
| '(' | push 0 | [0, 0, 0] |
| ')' | pop 0: max(1,0)=1, add to top | [0, 1] |
| '(' | push 0 | [0, 1, 0] |
| '(' | push 0 | [0, 1, 0, 0] |
| ')' | pop 0: 1, add to top | [0, 1, 1] |
| ')' | pop 1: 2*1=2, add to top | [0, 3] |
| ')' | pop 3: 2*3=6, add to top | [6] |
Result: 6 ✓
The same answer without a stack
Every () pair contributes 2^(d-1), where d is the depth that pair sits at.
Nothing else in the string contributes anything, because (A) only ever doubles
what is inside it. So one counter and one pass are enough.
Track the depth as you scan, and whenever a ) closes immediately after a (,
add 2^(depth - 1) using the depth before the closing bracket drops it.
For s = "(()(()))":
| i | char | depth before | depth after | core pair? | added |
|---|---|---|---|---|---|
| 0 | ( | 0 | 1 | ||
| 1 | ( | 1 | 2 | ||
| 2 | ) | 2 | 1 | yes | 2^1 = 2 |
| 3 | ( | 1 | 2 | ||
| 4 | ( | 2 | 3 | ||
| 5 | ) | 3 | 2 | yes | 2^2 = 4 |
| 6 | ) | 2 | 1 | no | |
| 7 | ) | 1 | 0 | no |
Total 6, matching the stack version. The two ) that close a ) rather than a
( add nothing: they are the outer brackets whose doubling is already baked
into the exponents.
class Solution:
def scoreOfParentheses(self, s: str) -> int:
score = depth = 0
for i, ch in enumerate(s):
if ch == '(':
depth += 1
else:
depth -= 1
if s[i - 1] == '(': # a core "()" at this depth
score += 1 << depth
return scoreO(n) time and O(1) space, against O(n) space for the stack. Worth knowing, but say the stack version first in an interview — it is the one that generalises when the scoring rule changes.
Common Mistakes
-
Stack approach: initializing stack to
[]instead of[0]. The base score 0 at index 0 represents the outermost scope. Without it, addingmax(2*v, 1)tostack[-1]on the first)would fail. -
Using
2*vwhenv == 0(empty pair). When the scope's score is 0, we have an atomic()which scores 1, not2 * 0 = 0. Always usemax(2*v, 1). -
Depth trick: forgetting to decrement depth before the check. The depth of the
()pair is the depth after the closing), not before. Decrement first, then add1 << depth. -
Depth trick: accessing
s[i-1]wheni == 0. The first character is always(— it can never be)that follows(, soi >= 1is guaranteed when the condition fires. But always be mindful of index bounds. -
Overflow with
1 << depthin Java/C++. The maximum depth isn/2 = 25.1 << 25 = 33,554,432— within 32-bit integer range. No overflow concern here, but in general be cautious with bit shifts.
Interview Tips
- Present both solutions: "The stack approach is intuitive — I maintain a running score at each nesting level. The depth trick is the O(1) optimization: every atomic
()at depth d contributes 2^d." - Explain why
max(2*v, 1): "If the scope had score 0 (empty), it was()which scores 1. If it had score v > 0, it was(A)which scores2*A. Themaxhandles both cases cleanly." - For the depth insight: "The multiplication rule
(A) = 2*Ameans each level of nesting doubles the contribution of the innermost(). An atomic()at depth d has been doubled d times → contributes 2^d."
Follow-up Questions
- Decode String (LC 394) — same stack save-and-restore pattern with repetition counts.
- What if
()scores 1 and(A) = A + 1? Rewrite the closing bracket case:stack[-1] += v + 1instead ofmax(2*v, 1). - Can the depth trick be used for Decode String? Not directly — Decode String has explicit counts, so the depth alone is insufficient.
- Minimum additions to make the score reach target T. Binary search or greedy on the structure.
- Return the list of scores for each depth level. Extend the stack approach to track contributions per depth instead of summing.
Key Takeaways
- Stack approach: push 0 on
(; on), pop scope scorevand addmax(2*v, 1)to the parent.max(2*v, 1)handles both()(score 1) and(A)(score 2*A). - Depth trick (O(1) space): each atomic
()at depth d contributes2^d = 1 << depthto the total. Track depth with a counter; add1 << depthwhenever you see a)that immediately follows(. - The bit-shift insight emerges from the observation that
(A) = 2*Ais geometric doubling — an()at depth d has been doubled d times. - Always initialize the stack with
[0]— the base scope — not[]. - The
max(2*v, 1)formula elegantly handles both the base case()and the recursive case(A)in one line. - This problem rewards mathematical insight over brute-force stack simulation and is a good test of whether you can derive the O(1) optimization.
Advertisement