Problem overview
The method reverseParentheses receives a string s made of lowercase letters and balanced parentheses. It must reverse the text inside every pair of matching parentheses, starting from the innermost pair, and return a string with no parentheses left.
Take "(u(love)i)". The innermost pair holds love, which becomes evol. The string is now (uevoli). Reversing the outer pair turns uevoli into iloveu, so the answer is "iloveu". The deepest example, "(yme(oc(da))gla)", goes through three reversals: da becomes ad, then ocad becomes daco, and finally the whole text becomes algocademy.
Approach: one buffer per nesting level
The simplest idea is to find the innermost pair, reverse it, delete its parentheses and repeat. That rescans the string many times and shifts characters around on every round. A single left-to-right pass is cleaner.
Think of each ( as opening a fresh piece of paper. Every letter you read is written on the top piece. When a ) arrives, the top piece is complete: reverse it, throw the paper away and write the reversed text onto the piece underneath. That piece may itself be inside parentheses, and it will be reversed later, together with everything written on it. This is exactly the "innermost first" order the task asks for.
A stack gives you this behaviour: the most recently opened level is always the first one closed. Here the stack holds strings, one per open level, and the bottom one collects the text that sits outside every pair.
For "a(bc)d" the stack changes like this:
a ["a"]
( ["a", ""]
b ["a", "b"]
c ["a", "bc"]
) ["acb"]
d ["acbd"]
Each character is handled once, so the pass is fast. The only repeated work is reversing each buffer when its level closes.
Step by step
- Create the stack as an array of strings holding one empty string. That first entry collects characters that appear before any
(. It is aconstbecause the array's contents change but the variable is never reassigned. - Loop over the characters with
for...of, which gives you one character per iteration. - On
(, push an empty string. A new level starts and collects everything until its matching). - On
), pop the top buffer. It holds exactly the text between the parentheses being closed. - Reverse that text. Strings have no
reversemethod, so split it into an array of characters, reverse the array and join it back. - Append the reversed text to the buffer that is now on top. After the pop, that is the level one step out.
- On any other character, append it to the top buffer.
- Join the buffers and return the result. For balanced input only one buffer remains, so joining just returns it.
The solution in TypeScript
class Solution {
reverseParentheses(s: string): string {
const res: string[] = [""];
for (const c of s) {
if (c === "(") {
res.push("");
} else if (c === ")") {
const top = res.pop();
const reversedTop = top.split("").reverse().join("");
res[res.length - 1] += reversedTop;
} else {
res[res.length - 1] += c;
}
}
return res.join("");
}
}
The TypeScript details worth a closer look:
- The type of
pop(). An array'spop()returns the removed element, orundefinedwhen the array is empty. Because the input is guaranteed balanced, a)always has a buffer to pop. The lesson compiles with strict null checking off, sotopis treated as a plainstring. - Strings are immutable.
res[res.length - 1] += cbuilds a new string and stores it back into that slot of the array. It doesn't modify the old string. constand arrays.const resstops you from assigning a different array tores. It doesn't stoppush,popor writing tores[i].for...ofon a string. It walks the string one character at a time, socis a one-character string and the comparisonc === "("is a plain string comparison.- Annotations are not checked at run time. The
: string[]annotation helps the compiler and the editor, but the compiled JavaScript contains no types at all.
Complexity
Let n be the length of s.
Time: O(n²) in the worst case. Each character is appended once when it is read. But a character inside k nested pairs is copied again each time one of those pairs closes, once for the reversal and once for the append. A string such as ((((ab)))) therefore copies its letters at every level, and a deeply nested input makes the total grow with the square of its length. With n up to 2000, this is comfortably fast.
Space: O(n). The buffers together never hold more than the letters of the input, plus the temporary arrays created by split.
Edge cases and common mistakes
- No parentheses.
"ab"stays"ab", because everything lands in the first buffer. - Text around a pair.
"a(bc)d"gives"acbd": only the part inside the parentheses is reversed. - Side-by-side pairs.
"(ab)(cd)"gives"badc". Each pair is reversed on its own, and the pairs are not swapped with each other. - Reversing once at the end. Reversing the whole string once is not the same as reversing level by level. Each pair must be reversed at the moment it closes.
- Writing to the wrong buffer. After the pop, append to
res[res.length - 1], the new top. Appending to the popped one loses the text. - Calling
reverse()on a string."abc".reverse()is a type error, because only arrays have that method. Go throughsplit("")andjoin("").
Testing
"(abcd)"gives"dcba"."(u(love)i)"gives"iloveu"."(yme(oc(da))gla)"gives"algocademy"."a(b(c)d)e"gives"adcbe"."ab"gives"ab".