Start learning free

LESSON 264 OF 344 · BACKTRACKING

Generate Parentheses in JavaScript with Time Complexity Analysis

Practice O(2^n) Read the full explanation

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

A parentheses string is well-formed if and only if:

  • It is the empty string, or

  • It can be written as AB (A concatenated with B), where A and B are well-formed, or

  • It can be written as (A), where A is well-formed.


Example 1:

Input: n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]

Example 2:

Input: n = 1
Output: ["()"]

Note: 1 <= n <= 8


STEP 1 OF 9
    Ctrl + Enter to check

    JAVASCRIPT · BACKTRACKING

    Generate Parentheses in JavaScript, explained

    Understanding the Problem

    The core challenge of this problem is to generate all possible combinations of well-formed parentheses given n pairs. This problem is significant in various applications such as validating expressions in compilers, generating code, and more. A common pitfall is to generate invalid combinations, which do not have matching opening and closing parentheses.

    Approach

    To solve this problem, we can use a backtracking approach. Backtracking is a general algorithm for finding all (or some) solutions to computational problems, notably constraint satisfaction problems. The idea is to build the solution incrementally and remove those solutions that fail to satisfy the constraints at any point in time.

    Naive Solution

    A naive solution would be to generate all possible sequences of parentheses and then filter out the valid ones. However, this approach is not optimal because it generates a lot of invalid sequences, leading to unnecessary computations.

    Optimized Solution

    The optimized solution involves using backtracking to generate only valid sequences. We keep track of the number of opening and closing parentheses used so far and ensure that at any point, the number of closing parentheses does not exceed the number of opening ones.

    Algorithm

    Here is a step-by-step breakdown of the backtracking algorithm:

    1. Initialize an empty list to store the results.
    2. Define a recursive function that takes the current string, the number of opening parentheses used, and the number of closing parentheses used.
    3. If the current string's length is equal to 2 * n, add it to the results list.
    4. If the number of opening parentheses used is less than n, add an opening parenthesis and recurse.
    5. If the number of closing parentheses used is less than the number of opening parentheses, add a closing parenthesis and recurse.

    Code Implementation

    /**
     * Generate all combinations of well-formed parentheses.
     * @param {number} n - Number of pairs of parentheses.
     * @return {string[]} - List of all combinations of well-formed parentheses.
     */
    function generateParenthesis(n) {
        const result = [];
        
        // Helper function for backtracking
        function backtrack(current, open, close) {
            // If the current string's length is 2 * n, it's a valid combination
            if (current.length === 2 * n) {
                result.push(current);
                return;
            }
            
            // If we can add an opening parenthesis, do so and recurse
            if (open < n) {
                backtrack(current + '(', open + 1, close);
            }
            
            // If we can add a closing parenthesis, do so and recurse
            if (close < open) {
                backtrack(current + ')', open, close + 1);
            }
        }
        
        // Start the backtracking process with an empty string
        backtrack('', 0, 0);
        
        return result;
    }
    
    // Example usage:
    console.log(generateParenthesis(3)); // ["((()))","(()())","(())()","()(())","()()()"]
    

    Complexity Analysis

    The time complexity of this approach is O(4^n / sqrt(n)), which is derived from the Catalan number. The space complexity is O(n) due to the recursion stack.

    Edge Cases

    Potential edge cases include:

    • n = 0: The output should be an empty list.
    • n = 1: The output should be ["()"].

    These cases are handled naturally by the algorithm.

    Testing

    To test the solution comprehensively, consider the following test cases:

    • n = 0: Expected output is []
    • n = 1: Expected output is ["()"]
    • n = 2: Expected output is ["(())", "()()"]
    • n = 3: Expected output is ["((()))","(()())","(())()","()(())","()()()"]

    Use a testing framework like Jest or Mocha to automate these tests.

    Thinking and Problem-Solving Tips

    When approaching such problems, consider the following tips:

    • Break down the problem into smaller sub-problems.
    • Use recursion and backtracking to explore all possible solutions.
    • Ensure to prune invalid solutions early to optimize performance.

    Practice similar problems to improve your problem-solving skills.

    Conclusion

    Generating well-formed parentheses is a classic problem that can be efficiently solved using backtracking. Understanding and implementing this solution helps in mastering recursion and backtracking techniques, which are essential for solving many complex problems.

    Additional Resources

    For further reading and practice, consider the following resources:

    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free