Difficulty: Easy | Acceptance: 87.00% | Paid: No Topics: String, Stack
A valid parentheses string is either empty "", ”(” + A + ”)”, or A + B, where A and B are valid parentheses strings, and A and B have no common leading parentheses. The primitive decomposition of a valid parentheses string S is a decomposition: S = P_1 + P_2 + … + P_k, where P_i are primitive valid parentheses strings.
Return S after removing the outermost parentheses of every primitive string in the primitive decomposition of S.
- Examples
- Constraints
- Approach 1: Counting (Depth Tracking)
- Approach 2: Stack
- Approach 3: Primitive Decomposition
Examples
Input: s = "(()())(())"
Output: "()()()"
Explanation:
The input string is "(()())(())", with primitive decomposition "(()())" + "(())".
After removing outer parentheses of each part, this is "()()" + "()" = "()()()".
Input: s = "(()())(())(()(()))"
Output: "()()()()(())"
Explanation:
The input string is "(()())(())(()(()))", with primitive decomposition "(()())" + "(())" + "(()(()))".
After removing outer parentheses of each part, this is "()()" + "()" + "()(())" = "()()()()(())".
Input: s = "()()"
Output: ""
Explanation:
The input string is "()()", with primitive decomposition "()" + "()".
After removing outer parentheses of each part, this is "" + "" = "".
Constraints
1 <= s.length <= 10⁵
s[i] is either '(' or ')'.
s is a valid parentheses string.
Approach 1: Counting (Depth Tracking)
Intuition We can iterate through the string and maintain a counter representing the current depth of nesting. The outermost parentheses correspond to a depth of 1. We only append characters to the result when the depth is greater than 1.
Steps
- Initialize a counter
openedto 0 and an empty list/string builder for the result. - Iterate through each character in the string.
- If the character is ’(’, increment
opened. Ifopenedis greater than 1 after incrementing, append the character to the result. - If the character is ’)’, decrement
opened. Ifopenedis greater than 0 after decrementing, append the character to the result. - Return the constructed result string.
class Solution:
def removeOuterParentheses(self, s: str) -> str:
res = []
opened = 0
for c in s:
if c == '(':
if opened > 0:
res.append(c)
opened += 1
else:
opened -= 1
if opened > 0:
res.append(c)
return ''.join(res)Complexity
- Time: O(n)
- Space: O(1) auxiliary space (excluding the space required for the output string).
- Notes: This is the most space-efficient approach as it only requires a single integer variable for tracking.
Approach 2: Stack
Intuition We can use a stack to simulate the nesting of parentheses. We push opening brackets onto the stack. When we encounter a closing bracket, we pop from the stack. If the stack is not empty, it means we are inside a primitive string, and we should add the character to our result.
Steps
- Initialize an empty stack and an empty list/string builder for the result.
- Iterate through each character in the string.
- If the character is ’(’, check if the stack is not empty. If so, append the character to the result. Then push the character onto the stack.
- If the character is ’)’, pop from the stack. If the stack is not empty, append the character to the result.
- Return the constructed result string.
class Solution:
def removeOuterParentheses(self, s: str) -> str:
res = []
stack = []
for c in s:
if c == '(':
if stack:
res.append(c)
stack.append(c)
else:
stack.pop()
if stack:
res.append(c)
return ''.join(res)Complexity
- Time: O(n)
- Space: O(n) in the worst case for the stack.
- Notes: While intuitive, the stack approach uses more memory than the counting approach, though both have linear time complexity.
Approach 3: Primitive Decomposition
Intuition We can identify the boundaries of each primitive string by tracking the balance of parentheses. When the balance returns to zero, we have found the end of a primitive. We then extract the substring excluding the first and last characters.
Steps
- Initialize a result list/string builder, a
startindex set to 0, and abalancecounter set to 0. - Iterate through the string with an index.
- Increment
balancefor ’(’ and decrement for ’)‘. - If
balancebecomes 0, we have reached the end of a primitive. Append the substring fromstart + 1to the current index (exclusive) to the result. Updatestartto the next index. - Return the joined result string.
class Solution:
def removeOuterParentheses(self, s: str) -> str:
res = []
start = 0
bal = 0
for i, c in enumerate(s):
if c == '(':
bal += 1
else:
bal -= 1
if bal == 0:
res.append(s[start+1:i])
start = i + 1
return ''.join(res)Complexity
- Time: O(n)
- Space: O(n) for the result string.
- Notes: This approach directly follows the problem definition of primitive decomposition but involves string slicing which can be slightly less efficient than character-by-character building in some languages.