A clear explanation of removing outermost parentheses from each primitive decomposition by tracking nesting depth.
Problem Restatement
A valid parentheses string is either empty, a concatenation of valid strings, or a valid string wrapped in parentheses.
A primitive valid parentheses string is one that cannot be split into two non-empty valid strings.
We are given a valid parentheses string s.
We need to remove the outermost parentheses of every primitive part in s and return the result.
The official constraints state that 1 <= s.length <= 10^5 and s consists of ( and ).
Input and Output
| Item | Meaning |
|---|---|
| Input | Valid parentheses string s |
| Output | s with outermost parentheses of each primitive removed |
Function shape:
def removeOuterParentheses(s: str) -> str:
...Examples
Example 1:
s = "(()())(())"Primitives: "(()())" and "(())".
After removing outer: "()()" and "()".
Answer:
"()()()"Example 2:
s = "(()())(())(()(()))"Primitives: "(()())", "(())", "(()(()))".
After removing outer: "()()", "()", "()(())".
Answer:
"()()()()(())"Example 3:
s = "()()"Primitives: "()" and "()".
After removing outer: "" and "".
Answer:
""Key Insight
Track the nesting depth as we scan the string.
A ( at depth 0 starts a new primitive (it is the outermost (). Don’t include it.
A ) that brings depth back to 0 ends a primitive (it is the outermost )). Don’t include it.
All other characters (at depth > 0 before update) are interior and should be included.
Algorithm
- Initialize
depth = 0and an empty result list. - For each character
cins:- If
c == '(':- If
depth > 0, appendcto result (not the outermost). - Increment
depth.
- If
- If
c == ')':- Decrement
depth. - If
depth > 0, appendcto result (not the outermost).
- Decrement
- If
- Return the joined result.
Correctness
Opening parentheses at depth 0 are outermost openers of primitives. By checking depth > 0 before appending, we skip them.
Closing parentheses that bring depth to 0 are outermost closers of primitives. By checking depth > 0 after decrement, we skip them.
All other characters are interior and correctly included.
Edge Cases
- The stack should store exactly the unresolved items needed by the invariant.
- Process equal values deliberately; many monotonic-stack problems differ only in
<versus<=. - Flush or inspect the remaining stack after the scan if unresolved items still contribute to the answer.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Single pass |
| Space | O(n) | Result array |
Common Pitfalls
- Do not optimize away the invariant; the code should still make it clear what condition is being maintained.
- Prefer problem-specific names over one-letter variables once the logic becomes stateful.
Implementation
class Solution:
def removeOuterParentheses(self, s: str) -> str:
result = []
depth = 0
for c in s:
if c == '(':
if depth > 0:
result.append(c)
depth += 1
else:
depth -= 1
if depth > 0:
result.append(c)
return "".join(result)Code Explanation
For (:
if depth > 0:
result.append(c)
depth += 1We append only if we are already inside a primitive (depth > 0). Then increase depth.
For ):
depth -= 1
if depth > 0:
result.append(c)We decrease depth first. If depth is still > 0, this is an interior ).
Testing
def run_tests():
s = Solution()
assert s.removeOuterParentheses("(()())(())") == "()()()"
assert s.removeOuterParentheses("(()())(())(()(()))") == "()()()()(())"
assert s.removeOuterParentheses("()()") == ""
assert s.removeOuterParentheses("((()))") == "(())"
print("all tests passed")
run_tests()| Test | Expected | Why |
|---|---|---|
"(()())(())" | "()()()" | Two primitives stripped |
"(()())(())(()(()))" | "()()()()(())" | Three primitives stripped |
"()()" | "" | Two trivial primitives |
"((()))" | "(())" | Single nested primitive |