Given a string s
containing just the characters (
, )
, {
, }
, [
and ]
, determine if the input string is valid.
An input string is valid if:
For example:
s = "()"
should return true
s = "()[]{}"
should return true
s = "(]"
should return false
s = "([])"
should return true
s = "([)]"
should return false
Explain your approach to solve this problem. What data structures might be useful? What is the time and space complexity of your solution? Consider edge cases such as empty strings, strings with only one character, mismatched brackets, or strings with only opening or closing brackets.
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 basic idea is to try removing matching parentheses until we can't anymore. If we end up with nothing, then it was valid. We repeatedly look for the simplest thing we can do to reduce the size of the problem.
Here's how the algorithm would work step-by-step:
def is_valid_parentheses_brute_force(input_string):
mutable_string = list(input_string)
while True:
found_match = False
index = 0
while index < len(mutable_string) - 1:
# Need to check for pairs to remove
if (mutable_string[index] == '(' and mutable_string[index + 1] == ')') or \
(mutable_string[index] == '[' and mutable_string[index + 1] == ']') or \
(mutable_string[index] == '{' and mutable_string[index + 1] == '}'):
# Found a matching pair, so remove them
mutable_string.pop(index + 1)
mutable_string.pop(index)
found_match = True
# Reset index to start from the beginning
index = 0
continue
index += 1
# If no matches were found, exit the loop
if not found_match:
break
# If the string is empty, it's valid
return not mutable_string
The core idea is to use a special helper structure to keep track of opening parentheses. When we find a closing parenthesis, we check if it matches the last opening one we saw, and if so, remove the opening one. This efficiently tells us if the parentheses are balanced and correctly ordered.
Here's how the algorithm would work step-by-step:
def is_valid(parentheses_string):
parentheses_stack = []
for parenthesis in parentheses_string:
if parenthesis in ['(', '{', '[']:
parentheses_stack.append(parenthesis)
elif parenthesis in [')', '}', ']']:
# Check for early closing parenthesis without an opening one.
if not parentheses_stack:
return False
top_element = parentheses_stack.pop()
# Verify the closing parenthesis matches the last opened one.
if parenthesis == ')' and top_element != '(':
return False
if parenthesis == '}' and top_element != '{':
return False
if parenthesis == ']' and top_element != '[':
return False
# Ensure all opened parentheses were properly closed.
if not parentheses_stack:
return True
else:
return False
Case | How to Handle |
---|---|
Null or empty input string | Return true immediately as an empty string is considered valid (no unmatched brackets). |
String with only open brackets | The stack will not be empty at the end, resulting in a return of false. |
String with only close brackets | The stack will be empty at the beginning, causing an attempt to pop from an empty stack or an early return of false. |
String with mismatched bracket types, e.g., '({[}])' | When a closing bracket is encountered, the check against the top of the stack will fail the type comparison and return false. |
String with incorrectly nested brackets, e.g., '[(])' | The order of brackets will cause a mismatch when attempting to close them off correctly based on their types, returning false. |
String with an odd number of brackets, e.g., '(((' | The stack will not be empty at the end, meaning not all brackets were closed correctly, returning false. |
Very long input string (potential stack overflow with recursive solutions) | Use an iterative stack implementation to avoid potential stack overflow issues and ensure linear time complexity. |
String contains non-bracket characters | The algorithm should gracefully ignore non-bracket characters as they do not affect validity. |