Write a function that will generate all possible combinations of grammatically correct parentheses. The function should be able to work with n pairs of parentheses.
Given n = 3, an example solution set would be:
[ "((()))", "(())()", "()(())", "()()()", "(()())" ]
Looking forward to seeing your solutions!
Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!
Top comments (10)
The number of results is a Catalan number (see Catalan number on Wikipedia)
I made it using python.
Edit: Using inbuilt function for generating permutations is a bad idea. This code works fine till n = 5 but then it takes noticeably long long time to execute. For n = 6, it took 1 minute 19 seconds!
Quick formatting tip: type
python
after the three backticks to get python formatting (like'''python
but with the correct symbol).I like how you are using the features Python gives you, even if it took a while for me to get it — after a few minutes I realized that the inner
for
loop was a check to see if the stringi
is properly formatted.Thanks for this 👍 I was looking for something like this before posting but couldn't find anything.
Typescript!
First, solving it with numbers. Each solution string is represented by an array of numbers with number incrementing for "(" and decrementing for ")".
For example, "(())()" is [1, 2, 1, 0, 1, 0]
The recursive helper function "solveWithNumbers" receives the beginning of a solution and returns an array with all complete solutions starting with these numbers.
The function "paren" runs "solveWithNumbers" and then converts the solution to the required format
[stackblitz.com/edit/typescript-n51vvd]
I tried using C++. Haven't tested it much. I know there exists a method in C++ to generate all permutations of a string. But this program is better optimized. It doesn't check all permutations. It stops following a sequence whenever it runs into a condition from where a valid sequence cannot be generated. For example, whenever number of closing braces are more than number of opening braces, there is no point following that recursion because it will never generate any valid permutations.
This is another case where dynamic programming is useful.
Refactoring the problem as "Give me all the valid (eg. don't end in an open) parenthesis combos of some given string length which, in aggregate, close some given number of parentheses along their length"
Then we get a nice substructure to work with: the result for some given length is just an easy convolution on the result for a smaller length.
Hmm, that came out a bit confusing.
Hopefully the code is easier to follow...
TypeScript:
A couple of runs...
I whipped this one up in PHP, going for readability first. As I'm building the solutions from smaller solutions using recursion with a lot of duplicate requests, memoization here is a must.
Also, damn, this requires a lot of memory. At
n=16
it crashes when trying to claim 2GB of memory when it already has 5GB used. I'll try to see how far I can get with some optimizations, but I'm not holding my breath on this one.Yeah, here is the most memory efficient one I could think of:
It now fails with a different amount of memory allocated, but still doesn't reach 16.
With the way it grows, I'm guessing the number of strings for 16 pairs of parentheses to be around 30 million. With each string being 33 characters (16 opening + 16 closing parenthesis, + 1 null terminator) I'm looking at almost 8 GB of memory for the strings alone. No wonder my laptop can't compute it.
This is an
O(n!)
because the output isO(n!)
- there is no way around it. Still - it can be greatly optimized if we generate the valid combinations in order instead of generating all the combinations and filtering for the valid ones - which both increases the number of combinations we need to generate and forces us to pay for validating each configuration.First, we need to represent the combinations. It is enough to represent the positions of the opening parenthesis, as we can fill the closing parenthesis when we generate the string. So our data structure will be an
n
sized vector of positions. The first position is always 0, but we'll keep it in the vector anyway for the sake of simplicity.What makes a representation valid? The first position -
p[0]
- must be 0. Technically this means we can just use ann-1
sized vector, but we'll keep it in the vector anyway for the sake of simplicity.What about the other position? For each
k
in[1,n)
, what are the valid values forp[k]
? Obviouslyp[k] > p[k-1]
so we have a minimum. The maximum is less obvious but also easy -k * 2
. This is the maximum number of characters before it -k
opening parenthesis andk
closing parenthesis. There cannot be more opening parenthesis becausek
is thek
th, and there cannot be more closing parenthesis because then there won't be enough opening parenthesis to match them.So, if we have
p[0], p[1], ... p[k]
we know the valid ranges ofp[k+1]
- and that's enough to advancep
:You can see it in action at play.rust-lang.org/?version=stable...
On my machine, when built with release, it can generate all 35,357,670 combinations for
n=16
in between 4 and 5 seconds. Printing them takes much much much longer - even when I redirect the output to/dev/null
it takes half a minute, and when I don't it takes forever (didn't let it finish). I suspect other implementations will have similar problems, so you should try to time them without printing the combinations (just print how many you got)