Before we start solving this problem, let’s do some math first. The problem statement clearly states that we have N pairs of parentheses to work with. That means for a string to be considered as part of the solution, it must be of size 2N.

Once we have generated a string of size 2N, for it to actually be a part of the solution, we will have to check if there is a balance in the opening and closing parentheses. If these conditions are met, the string is a part of the solution.

How many strings can be generated? At every index in the string we have 2 options, i.e. either a ( or a ) and we have to make this selection 2N times, that gives us a total of 2^2N strings.

For example, with N = 3, the length of the string should be 6 and there will be 2x2x2x2x2x2 or 2^6 strings that can be generated. Once a string has been generated, it will take us O(N) time to scan the string and validate it.

This crude algorithm will give us a time complexity of O(Nx2^2N) where N comes from scanning and 2^2N comes from generating. The space complexity can be limited to O(2^2N) for holding all the generated strings.

To make this solution more efficient, we should come up with a way that allows us to check if it’s okay to add a ( or a ) and this can be done by keeping count. We know for a fact that at any given point the number of ( has to be less than or equal to N and for the string to be balanced, the number of ) has to be less than or equal to (. This is the crucial part. I messed up my initial implementation of the code by assuming that ) also has to be less than or equal to N and as a result I started generated unbalanced strings.

This is a classic backtracking problem and can be solved using recursion. The code to do this looks something like:

func generateParenthesis(_ n: Int) -> [String] {
  var result = [String]()
  var s: String = ""
  
  func backtrack(numOpen:Int = 0, numClose: Int = 0) {
    if s.count == 2 * n {
      result.append(s)
      return
    }
    
    if numOpen < n {
      s.append("(")
      backtrack(numOpen: numOpen + 1, numClose: numClose)
      s.removeLast()
    }
    
    if numClose < numOpen {
      s.append(")")
      backtrack(numOpen: numOpen, numClose: numClose + 1)
      s.removeLast()
    }
  }
  
  backtrack()
  return result
}

Time and Space Complexity: We have only generated only those strings that will be a part of the valid solution which is essentially a subset of the 2^2N elements. It turns out this is the Nth Catalan number which is basically the kind of math I am unfamiliar with! Leetcode has a not so detailed instruction on this.

Code hosted on Github.