Problem overview
The method reverseParentheses receives a string s of lower case letters and balanced parentheses. It reverses the text inside each pair of matching parentheses, starting with the innermost pair, and returns the result without any parentheses.
For "(abcd)" the answer is "dcba". For "(u(love)i)", first love is reversed to evol, giving (uevoli), and then the whole group is reversed, giving "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
Each pair of parentheses opens a new nesting level, and the text in it is finished when its closing parenthesis arrives. At that moment you reverse the text and treat it as ordinary text of the level outside. This is last-in, first-out behaviour: the most recently opened level is the first one to close. A stack fits.
The lesson keeps a stack of strings, one per open level, with the current level on top:
- The stack starts with one empty string for the text before any parenthesis.
- An opening parenthesis pushes a new empty string.
- A closing parenthesis pops the top string, reverses it, and appends it to the string below.
- Any letter is appended to the top string.
When the input has been read, the stack holds one string, the answer. Reading "a(bc)d": the buffer holds a. The ( adds a second buffer, which collects bc. The ) removes it, reverses it to cb and appends it to the first buffer, giving acb. The letter d then makes it acbd.
Because inner groups are reversed when they close, and the result is added to the outer buffer, the outer group sees already-reversed text. When the outer group closes, the whole buffer is reversed again, which is exactly the "innermost first" order the statement describes.
Step by step
- Create the stack. A
List<String>namedres, built as anArrayListfromList.of(""). It starts with one empty string.List.ofalone returns an unmodifiable list, so the copy into anArrayListis what lets the list grow and shrink. - Loop over the characters with
for (char c : s.toCharArray()). - On
(, add an empty string. It collects the text up to the matching). - On
), remove the last element, at indexres.size() - 1, intotop. This is the group being closed. - Reverse it. Wrap
topin aStringBuilder, callreverse()and convert back withtoString(), givingreversedTop. - Merge it downward. After the removal, the buffer for the enclosing level is the last element. Replace it, using
set, with its old value plusreversedTop. - On any other character, append it to the last element the same way, with
setandget. - Return the joined stack.
String.join("", res)concatenates the entries with an empty separator. For balanced input only one entry remains, so this returns it.
The solution in Java
class Solution {
public String reverseParentheses(String s) {
List<String> res = new ArrayList<>(List.of(""));
for (char c : s.toCharArray()) {
if (c == '(') {
res.add("");
} else if (c == ')') {
String top = res.remove(res.size() - 1);
String reversedTop = new StringBuilder(top).reverse().toString();
res.set(res.size() - 1, res.get(res.size() - 1) + reversedTop);
} else {
res.set(res.size() - 1, res.get(res.size() - 1) + c);
}
}
return String.join("", res);
}
}
The Java details worth a closer look:
- Strings are immutable. You can't change a character of a
Stringor append to it in place. The expressionres.get(i) + cbuilds a new string, andsetstores that new string in the list, replacing the old one. Callinggetalone and adding would change nothing in the list. removetakes an index here.resis aList<String>, sores.remove(res.size() - 1)passes anintand removes the element at that position, returning it. The other overload,remove(Object), removes the first matching value. With aList<Integer>the two could be confused.String + charappends the character. Adding acharto aStringuses string concatenation, so"ab" + 'c'is"abc". It would add numbers if you added acharto anint.- No
reverseonString.StringBuilderhas areverse()method. It reverses in place and returns the same builder, so chaining.toString()gives the reversed string. - Comparing chars.
c == '('compares two primitivecharvalues with==, which is correct. Characters use single quotes and strings use double quotes. - The stack is a list.
ArrayListadds and removes at its end in constant time, so the last element serves as the stack's top.Dequeis a common alternative.
Complexity
Let n be the length of s.
Time: O(n²) in the worst case. Each letter is appended by building a new string, which copies the buffer. Each closing parenthesis reverses and copies a buffer too. A buffer holds at most n characters, and there are at most n such operations. With n up to 2,000 this is a few million character copies, which is fast.
Space: O(n). The buffers together hold at most n characters, plus temporary copies of one buffer at a time.
Edge cases and common mistakes
- No parentheses.
"abc"returns"abc". Only the first buffer is ever used. - Nested groups.
"((ab))"is reversed twice and returns"ab". - Adjacent groups.
"(ab)(cd)"returns"badc". Each group is reversed on its own, and the results are joined in order. - An empty pair.
"a()b"returns"ab". The empty buffer is reversed to an empty string and appended. - Deep nesting. A thousand nested pairs around one letter work, since the stack is a heap-allocated list and no recursion is used.
- Reversing the wrong buffer. The reversal applies to the string just removed, not to the enclosing one.
- Forgetting to store back. Writing
res.get(res.size() - 1) + cas a statement computes a string and throws it away.
Testing
"(abcd)"gives"dcba"."(u(love)i)"gives"iloveu"."(yme(oc(da))gla)"gives"algocademy"."a(bc)d"gives"acbd"."(ab)(cd)"gives"badc".