Given a string s
containing only three types of characters: (
, )
, and *
, return true
if s
is valid.
The following rules define a valid string:
(
must have a corresponding right parenthesis )
.)
must have a corresponding left parenthesis (
.(
must go before the corresponding right parenthesis )
.*
could be treated as a single right parenthesis )
, a single left parenthesis (
, or an empty string ``.For example:
()
is valid.(*)
is valid because the *
can be a (
.(*))
is valid because the first *
can be a (
.((*)
is valid because the *
can be a )
.)
is invalid.)(
is invalid.Write a function to determine if the input string is valid according to these rules. Explain the time and space complexity of your solution.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to this parenthesis problem means we explore every single possible arrangement of the string. We will consider all the ways to treat asterisks as either open parenthesis, close parenthesis, or empty strings. Then, we'll check each arrangement to see if it's valid.
Here's how the algorithm would work step-by-step:
def is_valid_parenthesis_string_brute_force(input_string):
def check_valid(string_to_check):
balance = 0
for char in string_to_check:
if char == '(':
balance += 1
elif char == ')':
balance -= 1
if balance < 0:
return False
return balance == 0
def generate_combinations(current_string, index):
if index == len(input_string):
# If no more asterisks, check validity
if '*' not in current_string:
return check_valid(current_string)
else:
return False
if input_string[index] != '*':
return generate_combinations(current_string + input_string[index], index + 1)
else:
# Explore all 3 possibilities for each asterisk
return (generate_combinations(current_string + '(', index + 1) or \
generate_combinations(current_string + ')', index + 1) or \
generate_combinations(current_string, index + 1))
# Kick off the recursive process of exploring all combinations
return generate_combinations('', 0)
The key is to keep track of the possible range of open parentheses we could have at any point. We use two counters to track the minimum and maximum possible open parentheses, updating them as we go through the string. If the minimum ever goes negative, we know it's invalid.
Here's how the algorithm would work step-by-step:
def is_valid_string(input_string: str) -> bool:
minimum_open = 0
maximum_open = 0
for character in input_string:
if character == '(':
minimum_open += 1
maximum_open += 1
elif character == ')':
minimum_open -= 1
maximum_open -= 1
else:
# Star can be ( or ) or empty
minimum_open -= 1
maximum_open += 1
# If max open becomes negative, it is invalid
if maximum_open < 0:
return False
# Minimum open can't be negative
minimum_open = max(minimum_open, 0)
# String is valid if min open is 0 at the end
return minimum_open == 0
Case | How to Handle |
---|---|
Null or empty input string | Return true immediately as an empty string is considered valid. |
String with only '*' characters | Return true since all '*' can be treated as empty strings. |
String with only '(' characters | Return false as there are no corresponding ')' characters. |
String with only ')' characters | Return false as there are no corresponding '(' characters. |
String with many '(' at the beginning and many ')' at the end | The algorithm needs to efficiently handle deeply nested unbalanced parenthesis; the two-pass approach (left-to-right and right-to-left) correctly accounts for these unbalanced edge cases. |
String with a high number of '*' characters interleaved with '(' and ')' | The algorithm considers all possible interpretations of '*' by maintaining low and high bounds, effectively pruning the search space of the exponential number of possibilities. |
String like '(*)' where '*' can balance the string | The low and high bounds will eventually both be zero at the end of iteration, resulting in true. |
Very long input string (close to memory limit) | The space complexity of the algorithm is O(1), meaning the algorithm scales well with large inputs. |