A clear explanation of scoring a balanced parentheses string using depth counting.
Problem Restatement
We are given a balanced parentheses string s.
The score is defined by three rules:
| Form | Score |
|---|---|
"()" | 1 |
AB | score(A) + score(B) |
(A) | 2 * score(A) |
Here, A and B are balanced parentheses strings.
We need to return the score of s.
Input and Output
| Item | Meaning |
|---|---|
| Input | A balanced parentheses string s |
| Output | The integer score of s |
| Constraint | 2 <= s.length <= 50 |
| Constraint | s contains only '(' and ')' |
| Guarantee | s is balanced |
Function shape:
class Solution:
def scoreOfParentheses(self, s: str) -> int:
...Examples
Example 1:
s = "()"The string is the base case.
score("()") = 1So the answer is:
1Example 2:
s = "(())"The inner string is:
()which has score 1.
The outer parentheses double it:
2 * 1 = 2So the answer is:
2Example 3:
s = "()()"This is two balanced strings side by side:
"()" + "()"Each has score 1, so the total is:
1 + 1 = 2Example 4:
s = "(()(()))"Inside the outer pair, we have:
"()" + "(())"Their scores are:
1 + 2 = 3The outer pair doubles the result:
2 * 3 = 6So the answer is:
6First Thought: Use a Stack
The scoring rules are recursive.
A natural way to model them is with a stack.
When we see '(', we start a new nested group.
When we see ')', we close the current group.
If the current group is empty, then it represents "()", with score 1.
If the current group already has a score, then wrapping it in parentheses doubles it.
This works, but there is an even simpler way.
Key Insight
Only the primitive pair "()" directly contributes score.
Outer parentheses only multiply that contribution by powers of two.
For example:
s = "(())"The primitive pair "()" is inside one outer pair, so its contribution is:
2For:
s = "((()))"The primitive pair is inside two outer pairs, so its contribution is:
4In general, when we see a primitive pair "()" at depth d, it contributes:
2^dwhere d is the number of outer pairs around it.
So we scan the string, track the current depth, and add a value whenever we find "()".
Algorithm
Maintain two variables:
score = 0
depth = 0Scan the string from left to right.
For each character:
- If the character is
'(', increasedepth. - If the character is
')', decreasedepth. - If the current character is
')'and the previous character is'(', then we found a primitive pair"()". - Add:
1 << depthto the score.
The order matters.
When we see ')', we first decrease depth.
After decreasing, depth equals the number of outer pairs around this primitive "()".
Correctness
Every balanced parentheses string can be decomposed into primitive pairs "()", with some number of outer parentheses around each primitive pair.
The scoring rules imply that concatenation adds scores, while wrapping in parentheses doubles the score.
Therefore, each primitive pair contributes 1 multiplied by 2 once for every outer pair around it.
So a primitive pair at outer depth d contributes 2^d.
The algorithm finds exactly the primitive pairs by checking where a ')' is immediately preceded by '('.
For each such pair, after decrementing depth, the variable depth equals the number of outer pairs surrounding that primitive pair. The algorithm adds 2^depth, which is exactly that pair’s contribution.
Since every score contribution comes from one primitive pair, and every primitive pair is counted once, the final score is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the string once |
| Space | O(1) | We only store score and depth |
Implementation
class Solution:
def scoreOfParentheses(self, s: str) -> int:
score = 0
depth = 0
for i, char in enumerate(s):
if char == "(":
depth += 1
else:
depth -= 1
if s[i - 1] == "(":
score += 1 << depth
return scoreCode Explanation
We start with:
score = 0
depth = 0score stores the answer.
depth stores how many open parentheses are currently active.
When we see an opening parenthesis:
if char == "(":
depth += 1we move one level deeper.
When we see a closing parenthesis:
else:
depth -= 1we close one level.
After closing, if the previous character was '(', then the current pair is a primitive "()":
if s[i - 1] == "(":
score += 1 << depthThe expression:
1 << depthmeans 2^depth.
For example:
depth | 1 << depth |
|---|---|
0 | 1 |
1 | 2 |
2 | 4 |
3 | 8 |
Finally:
return scorereturns the total score.
Testing
def test_score_of_parentheses():
s = Solution()
assert s.scoreOfParentheses("()") == 1
assert s.scoreOfParentheses("(())") == 2
assert s.scoreOfParentheses("()()") == 2
assert s.scoreOfParentheses("(()(()))") == 6
assert s.scoreOfParentheses("((()))") == 4
assert s.scoreOfParentheses("((())())") == 6
print("all tests passed")
test_score_of_parentheses()Test meaning:
| Test | Why |
|---|---|
"()" | Base score |
"(())" | One nested pair |
"()()" | Concatenation |
"(()(()))" | Nested and concatenated groups |
"((()))" | Multiple layers around one primitive pair |
"((())())" | Mixed nesting inside one outer pair |