Problem overview
The method reverseParentheses receives a string s of lowercase letters and balanced parentheses. It must reverse the text inside each pair of matching parentheses, starting with the innermost pair, and the result must contain no parentheses.
For "(u(love)i)" the inner pair holds love, which becomes evol. The string is now (uevoli), and reversing the outer pair gives iloveu. For "(yme(oc(da))gla)" the steps are da to ad, then ocad to daco, then the whole content ymedacogla to algocademy.
Approach: one buffer per open parenthesis
The innermost-first rule is a signal for a stack. Every ( starts a new level of text, and the matching ) ends the most recently opened one, so the last level opened is the first one closed.
Keep a stack where each item is a string buffer collecting the characters of one level. Ordinary letters go to the buffer on top. When a ( arrives, push a new empty buffer. When a ) arrives, pop the top buffer, reverse it, and append it to the buffer that is now on top, because that text belongs to the enclosing level. Reversal happens at the moment a level closes, so inner levels are reversed before the outer ones that contain them. This gives exactly the order the problem asks for.
Here is the stack for "(u(love)i)" after each character:
| Character | Stack afterwards |
|---|---|
( | ['', ''] |
u | ['', 'u'] |
( | ['', 'u', ''] |
l, o, v, e | ['', 'u', 'love'] |
) | ['', 'uevol'] |
i | ['', 'uevoli'] |
) | ['iloveu'] |
Step by step
- Start with one empty buffer.
res = ['']. The bottom buffer collects characters that sit outside every pair of parentheses, so the stack is never empty while the loop runs. - Loop over the characters of
swith the variablec. - Open a level on
(. Append''tores. Everything up to the matching)will be collected in it. - Handle
)in anelif. This branch closes the level on top. - Pop the top buffer with
res.pop()intotop. It is the text between the parentheses just closed. - Reverse it with the slice
top[::-1]and call the resultreversed_top. - Append it to the new top,
res[-1] += reversed_top. After the pop, the enclosing level's buffer is last, and the reversed text is part of it. - Handle every other character in the
elsebranch:res[-1] += cadds it to the current level. - Join and return.
"".join(res)combines the buffers. For balanced input only the bottom buffer remains, so the join returns that single string.
The solution in Python
class Solution:
def reverseParentheses(self, s: str) -> str:
res = ['']
for c in s:
if c == '(':
res.append('')
elif c == ')':
top = res.pop()
reversed_top = top[::-1]
res[-1] += reversed_top
else:
res[-1] += c
return "".join(res)
Python details to notice:
- A list works as a stack.
appendpushes at the end,pop()without an argument removes and returns the last item, andres[-1]reads the top without removing it. - Slicing with step -1.
top[::-1]walks the string backwards and returns a new string. Python has noreverse()method on strings, so a slice (or"".join(reversed(top))) is the usual way. - Strings are immutable.
res[-1] += cbuilds a new string and stores it back into that slot of the list. The old string is not changed in place. "".join(res)joins the strings with nothing between them. It is the standard way to combine a list of strings.
Complexity
Time: O(n²) in the worst case, where n is the length of s. Every ) reverses a buffer and copies it into the buffer below, so the work per closing parenthesis is the length of the text inside. When pairs are nested, the same letters are copied again at every level. Take 500 opening parentheses, then 1,000 letters, then 500 closing parentheses: each of the 500 closings copies a buffer of 1,000 letters, which is about 500,000 character copies. Ordinary strings are far cheaper, and with the limit of 2,000 characters even this worst case runs instantly.
Space: O(n). The buffers together hold at most all the letters of the string.
Edge cases and common mistakes
- No parentheses.
"abc"stays"abc", since everything goes into the bottom buffer. - Text outside the pair.
"a(bc)d"gives"acbd". The lettersaanddremain in place around the reversed part. - Side-by-side pairs.
"(ab)(cd)"gives"badc". Each pair is reversed alone, and the pairs keep their order. - An empty pair.
"()"gives the empty string. - Appending to the wrong buffer. After popping, the reversed text must go to
res[-1]. Putting it back with a push would keep the level open. - Reversing the whole string at the end. That only works for a single outer pair. The reversal must happen once per closing parenthesis, on the level that just closed.
- Forgetting the bottom buffer. With
res = [], the first letter outside any parentheses would hitres[-1]on an empty list and raiseIndexError.
Testing
"(abcd)"gives"dcba"."(u(love)i)"gives"iloveu"."(yme(oc(da))gla)"gives"algocademy"."ab(cd(ef)g)h"gives"abgefdch".