Taro Logo

Minimum Remove to Make Valid Parentheses

Medium
Meta logo
Meta
160 views
Topics:
StringsStacksTwo Pointers

Given a string s of '(' , ')' and lowercase English characters.

Your task is to remove the minimum number of parentheses ( '(' or ')', in any positions ) so that the resulting parentheses string is valid and return any valid string.

Formally, a parentheses string is valid if and only if:

  • It is the empty string, contains only lowercase characters, or
  • It can be written as AB (A concatenated with B), where A and B are valid strings, or
  • It can be written as (A), where A is a valid string.

Example 1:

Input: s = "lee(t(c)o)de)"
Output: "lee(t(c)o)de"
Explanation: "lee(t(co)de)" , "lee(t(c)ode)" would also be accepted.

Example 2:

Input: s = "a)b(c)d"
Output: "ab(c)d"

Example 3:

Input: s = "))(("
Output: ""
Explanation: An empty string is also valid.

Constraints:

  • 1 <= s.length <= 105
  • s[i] is either '(' , ')', or lowercase English letter.

Solution


Clarifying Questions

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:

  1. What is the maximum length of the input string s?
  2. Can the input string s be empty or null?
  3. If there are multiple valid results (strings with the minimum number of removals), is any one acceptable, or is there a specific one I should return (e.g., the one that removes characters at the earliest indices)?
  4. Are the only characters allowed in the string '(' , ')' and lowercase English letters, or can there be other characters?
  5. Is the returned string expected to maintain the original relative order of the valid parentheses and other characters, or is rearranging them acceptable?

Brute Force Solution

Approach

The brute force method for making parentheses valid involves trying every possible combination of removals. We explore all possibilities by removing different parentheses one by one until we find a valid combination.

Here's how the algorithm would work step-by-step:

  1. Consider every possible combination of parentheses that could be removed from the original string.
  2. For each combination, create a new string with only the parentheses that were not removed.
  3. Check if the new string has valid parentheses – meaning, for every open parenthesis, there's a corresponding closing parenthesis in the correct order.
  4. If the string is valid, compare its length with the lengths of other valid strings you've found so far.
  5. Keep track of the longest valid string you find by going through all the combinations.
  6. After checking every possible combination, return the longest valid string.

Code Implementation

def minimum_remove_to_make_valid_parentheses_brute_force(input_string):
    longest_valid_string = ""

    # Generate all possible substrings by removing characters
    for i in range(1 << len(input_string)):
        current_string = ""
        indices_to_keep = []

        for j in range(len(input_string)):
            if (i >> j) & 1:
                current_string += input_string[j]
                indices_to_keep.append(j)

        # Check if the current string is valid
        if is_valid(current_string):

            # We want to keep the longest one found so far
            if len(current_string) > len(longest_valid_string):
                longest_valid_string = current_string

    return longest_valid_string

def is_valid(current_string):
    balance = 0
    for char in current_string:
        if char == '(': 
            balance += 1
        elif char == ')':
            balance -= 1

            # If balance becomes negative, it's invalid
            if balance < 0:
                return False
    # String is valid if balance is 0
    return balance == 0

Big(O) Analysis

Time Complexity
O(2^n * n)The brute force approach involves generating every possible subset of the input string of length n. There are 2^n such subsets. For each subset, we need to check if the parentheses are valid. Checking validity requires iterating through the subset which takes O(n) time in the worst case (the subset has all n characters). Therefore, the overall time complexity is O(2^n * n).
Space Complexity
O(2^N)The brute force approach generates all possible combinations of removals. Each combination can be represented as a string, and in the worst case, there could be 2^N combinations (where N is the length of the input string) as each character can be either kept or removed. Storing all these combinations in memory results in a space complexity that is exponential with respect to the input size. Additionally, the recursion stack for generating combinations can grow up to a depth of N, contributing O(N) space, which is dominated by the exponential term.

Optimal Solution

Approach

The key idea is to scan the input once to identify and mark the parentheses that need to be removed to make the string valid. We use a counter to keep track of open parentheses and then remove the extra ones that don't have a match.

Here's how the algorithm would work step-by-step:

  1. Go through the string from left to right.
  2. Keep track of how many opening parentheses you've seen.
  3. When you see a closing parenthesis, check if you have an opening parenthesis to match it with.
  4. If you do have a match, decrease the count of open parentheses because one opening parenthesis has now been matched.
  5. If you *don't* have a match, that means you have an extra closing parenthesis that needs to be removed.
  6. Now, go through the string from right to left.
  7. Keep track of how many closing parentheses you've seen.
  8. When you see an opening parenthesis, check if you have a closing parenthesis to match it with.
  9. If you do have a match, decrease the count of closing parentheses.
  10. If you *don't* have a match, that means you have an extra opening parenthesis that needs to be removed.
  11. Finally, build a new string containing only the characters that weren't marked for removal.

Code Implementation

def min_remove_to_make_valid(input_string: str) -> str:
    string_length = len(input_string)
    indices_to_remove = [False] * string_length
    open_parenthesis_count = 0

    for index in range(string_length):
        if input_string[index] == '(': 
            open_parenthesis_count += 1
        elif input_string[index] == ')':
            if open_parenthesis_count > 0:
                open_parenthesis_count -= 1
            else:
                # Mark extra closing parenthesis for removal.
                indices_to_remove[index] = True

    closing_parenthesis_count = 0

    for index in range(string_length - 1, -1, -1):
        if input_string[index] == ')':
            closing_parenthesis_count += 1
        elif input_string[index] == '(': 
            if closing_parenthesis_count > 0:
                closing_parenthesis_count -= 1
            else:
                # Mark extra opening parenthesis for removal.
                indices_to_remove[index] = True

    result = ''
    for index in range(string_length):
        if not indices_to_remove[index]:
            result += input_string[index]

    return result

Big(O) Analysis

Time Complexity
O(n)The algorithm involves three main steps: iterating through the string from left to right, iterating through the string from right to left, and building the final string. Each of these iterations processes each character in the input string once. Therefore, the time complexity is directly proportional to the length of the input string, n, resulting in O(n) time complexity.
Space Complexity

Edge Cases

CaseHow to Handle
Empty string inputReturn the empty string itself, as it's already a valid parentheses string of length 0.
String with only opening parenthesesRemove all opening parentheses to get an empty valid string.
String with only closing parenthesesRemove all closing parentheses to get an empty valid string.
String with balanced parentheses and other charactersThe solution correctly identifies and preserves the balanced parentheses and other characters.
String with nested unbalanced parenthesesThe algorithm efficiently removes only the necessary unbalanced parentheses.
String with a large number of charactersThe solution should have a time complexity of at most O(n) to handle large inputs efficiently, where n is the length of the string.
String with deeply nested parentheses that could potentially cause stack overflow issues if recursion were used inappropriatelyIterative solutions should be preferred to avoid potential stack overflow for deeply nested parentheses; Stack based solution should bound memory appropriately.
String with mixed valid and invalid parentheses scattered throughoutThe solution should maintain the relative order of valid parentheses and non-parenthesis characters while removing the minimum number of invalid parentheses.