Under the grammar given below, strings can represent a set of lowercase words. Let R(expr)
denote the set of words the expression represents.
The grammar can best be understood through simple examples:
R("a") = {"a"}
R("w") = {"w"}
R("{a,b,c}") = {"a","b","c"}
R("{{a,b},{b,c}}") = {"a","b","c"}
(notice the final set only contains each word at most once)R("{a,b}{c,d}") = {"ac","ad","bc","bd"}
R("a{b,c}{d,e}f{g,h}") = {"abdfg", "abdfh", "abefg", "abefh", "acdfg", "acdfh", "acefg", "acefh"}
Formally, the three rules for our grammar:
x
, we have R(x) = {x}
.e1, e2, ... , ek
with k >= 2
, we have R({e1, e2, ...}) = R(e1) ∪ R(e2) ∪ ...
e1
and e2
, we have R(e1 + e2) = {a + b for (a, b) in R(e1) × R(e2)}
, where +
denotes concatenation, and ×
denotes the cartesian product.Given an expression representing a set of words under the given grammar, return the sorted list of words that the expression represents.
Example 1:
Input: expression = "{a,b}{c,{d,e}}" Output: ["ac","ad","ae","bc","bd","be"]
Example 2:
Input: expression = "{{a,z},a{b,c},{ab,z}}" Output: ["a","ab","ac","z"] Explanation: Each distinct word is written only once in the final answer.
Constraints:
1 <= expression.length <= 60
expression[i]
consists of '{'
, '}'
, ','
or lowercase English letters.expression
represents a set of words based on the grammar given in the description.This problem involves parsing and evaluating a string expression based on a specific grammar involving braces {}
, commas ,
, and concatenation. The goal is to generate a sorted list of all possible strings that can be formed by following the grammar rules.
Understanding the Grammar
The grammar can be summarized as follows:
Base Case: A single lowercase letter represents a set containing only that letter. R("a") = {"a"}
Union: A comma-separated list within braces {}
represents the union of the sets represented by each element. R("{a,b,c}") = {"a", "b", "c"}
Concatenation: Concatenation of two expressions results in the Cartesian product of their respective sets. R("{a,b}{c,d}") = {"ac", "ad", "bc", "bd"}
Approach: Depth-First Search (DFS)
The most efficient way to solve this problem is using a recursive Depth-First Search (DFS) approach. The algorithm explores all possible combinations systematically.
Algorithm:
dfs(exp)
function: This recursive function takes an expression exp
as input.
Base Case: If exp
does not contain a closing brace }
, it means we have reached a valid string. Add this string to the s
set (using a set
to automatically handle uniqueness).
Recursive Step:
{
and the matching closing brace }
.dfs
with the updated expression (replacing the bracketed portion with the current option).Final Step: Once the DFS completes, convert the s
set into a sorted list and return it.
Time and Space Complexity Analysis:
Time Complexity: The time complexity is exponential in the worst case, O(2n), where n is the number of comma-separated options within the braces. This is because each option leads to a branching path in the DFS tree. However, it's often much faster in practice if there aren't deeply nested bracketed expressions with many options.
Space Complexity: The space complexity is also potentially exponential, O(2n), due to the recursive calls and the s
set storing all generated strings in the worst case. The space used by the recursive call stack can also contribute to the space complexity.
Code Implementation (Python):
class Solution:
def braceExpansionII(self, expression: str) -> List[str]:
s = set() # Use a set to automatically handle uniqueness
def dfs(exp):
j = exp.find('}')
if j == -1: # Base case: No more braces
s.add(exp)
return
i = exp.rfind('{', 0, j - 1) # Find the last opening brace
a, c = exp[:i], exp[j + 1:] # Split into parts before and after braces
for b in exp[i + 1:j].split(','): # Iterate through options
dfs(a + b + c) # Recursive call
dfs(expression)
return sorted(list(s)) # Convert set to sorted list
The implementations in Java, C++, Go, and TypeScript follow a similar logic, adapting the syntax to their respective languages. The core recursive DFS algorithm remains the same. Note that the use of a TreeSet
in Java provides automatic sorting, eliminating the need for a separate sort step. Similarly, set
in C++ and map
in Go provide automatic handling of uniqueness and implicitly sort the keys when iterated over. The TypeScript code explicitly uses a Set
for uniqueness and then sorts the resulting array.