Problem overview
The method reverseParentheses receives a string s made of lowercase letters and balanced parentheses. It reverses the text inside every pair of matching parentheses, starting with the innermost pair, and returns the result without any parentheses.
For "(u(love)i)" the innermost pair holds "love", which becomes "evol", so the string is now "(uevoli)". Reversing that whole group gives "iloveu". For "(yme(oc(da))gla)" the steps are "da" to "ad", then "ocad" to "daco", then the outer group "ymedacogla" to "algocademy".
Approach: one buffer per nesting level
The text inside a pair of parentheses can be finished only when its closing parenthesis arrives. Until then it may still receive more characters or inner groups. So keep a stack of strings, one for each level of nesting that is currently open. The last string on the stack collects characters for the innermost open group.
- On
(, open a new level: push an empty string. - On a letter, append it to the last string.
- On
), the last string is complete. Remove it, reverse it and append the result to the string below it, which is the enclosing level.
Reversing happens as each group closes, so inner groups are reversed before the outer ones that contain them, which is the order the problem asks for. At the start the stack holds one empty string, the base level that collects the text outside every pair.
Take "a(bc(de)f)g". The stack changes like this, after each character:
a ["a"]
( ["a", ""]
b ["a", "b"]
c ["a", "bc"]
( ["a", "bc", ""]
d ["a", "bc", "d"]
e ["a", "bc", "de"]
) ["a", "bced"]
f ["a", "bcedf"]
) ["afdecb"]
g ["afdecbg"]
The first ) reverses "de" and appends "ed" to "bc". The second reverses "bcedf" and appends "fdecb" to "a".
Step by step
- Create the stack as
val res, anArrayBuffer[String]holding one empty string. AnArrayBuffercan grow and shrink, and its last element is the top of the stack. - Loop over the characters with
for (c <- s). AStringcan be used directly as a sequence ofCharvalues. - Handle
(. Append an empty string withres += ""to start a new level. - Handle
). Remove the last element withres.remove(res.length - 1), which returns the removed string, and keep it intop. - Reverse it with
top.reverse. - Merge it downwards. After the removal, the enclosing level is the last element, so set
res(res.length - 1)to its old value plus the reversed text. - Handle every other character by appending it to the last element in the same way.
- Join the buffers with
mkString("")and return the result. For balanced input only the base level is left, so the join returns that single string.
The solution in Scala
class Solution {
def reverseParentheses(s: String): String = {
val res = scala.collection.mutable.ArrayBuffer[String]("")
for (c <- s) {
if (c == '(') {
res += ""
} else if (c == ')') {
val top = res.remove(res.length - 1)
val reversedTop = top.reverse
res(res.length - 1) = res(res.length - 1) + reversedTop
} else {
res(res.length - 1) = res(res.length - 1) + c
}
}
return res.mkString("")
}
}
Scala details:
valfixes the reference, not the contents.rescan't be reassigned, but its buffer can still grow, shrink and have elements replaced.res(i) = valuecallsupdateon the buffer, andres(i)reads withapply. Both use parentheses, not square brackets.remove(index)returns the element it removes. That is why one call both takes the top string and shortens the stack.reverseon aStringtakes no parentheses and returns a new string. Strings are immutable, so the original is unchanged.- String plus Char.
res(res.length - 1) + cappends the character and gives a new string. Each append copies the string, which is acceptable for a limit of 2000 characters. - The
returnkeyword is optional on the last line. It is legal here because it is not inside a closure: the loop body is a function passed toforeach, and areturninside it would leave the method from within that function.
Complexity
Time: O(n^2) in the worst case. Every character is handled once, but appending a character to a string copies the string, so building one group of k characters costs about k^2 / 2 copied characters. Reversing and merging a group copies it again at each closing parenthesis, and a deeply nested input copies its inner text once per level. With n at most 2000 this is far below any limit.
Space: O(n). All the buffers together hold at most the characters of the input, and the stack has at most one entry per open parenthesis plus one.
Edge cases and common mistakes
- No parentheses.
"abc"returns"abc": only the base level exists. - Text outside the pairs stays in place.
"ab(cd)ef"returns"abdcef". - Sibling groups.
"a(bc)(de)f"returns"acbedf". Each group is reversed on its own, and the groups are not reversed relative to each other. - Double parentheses.
"((a))"returns"a": the single letter is reversed twice. - Nested groups.
"a(b(c)d)e"returns"adcbe". Reversing"c"changes nothing, so the outer group holds"bcd", which is reversed to"dcb". - Reversing at the wrong time. Reversing the buffer on
(or at the end of the loop instead of on)gives wrong answers for nested groups, because an inner group must be finished before the outer one that contains it. - Dropping the base level. Without the initial empty string, a letter before the first
(would have no buffer to go into.
Testing
"(abcd)"returns"dcba"."(u(love)i)"returns"iloveu"."(yme(oc(da))gla)"returns"algocademy"."a(bc)(de)f"returns"acbedf".
