1

I recently had an interview where I was asked to solve a modified version of this question on leetcode. Instead of checking if the string is valid you are supposed to count the minimum number of brackets that need to be removed in order for the string to be valid.

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:
- Open brackets must be closed by the same type of brackets.
- Open brackets must be closed in the correct order.
- Every close bracket has a corresponding open bracket of the same type.

We had discussed some possible outcomes, for example:
- "( [ ) ]" should return 2
- "( [ { } ] )" should return 0
- "( [ ] ] )" should return 1

A test case I thought of after the interview:
- "( [ { ] )" should return 1, but my solution returns 5

I solved it using a stack which basically pushes open brackets, pops corresponding closing brackets, and counts unmatched closing brackets, returns size of the stack plus the unmatched closing brackets, and it passed all of these test cases. After the interview I had thought of a case which my code would fail, "( [ { ] )" returns 5 where it should return 1. If I think of a different solution I can come up with something that would return 1 but when you test "( [ ) ]" it returns 0. I can't figure out how to correctly account for both cases.
Any insight would be helpful.

1
  • Funny that mimimum amount of paranthesis added to make it valid has the same solution. This is because removing a paranthesis or adding a matching paranthesis to it both make it valid. Commented Jun 17 at 15:32

4 Answers 4

3

I’d try the following:

Let f(i, j) represent the least removals for substring S[i..j]
  where i < j
  and S[j] is some closing parenthetical

Then f(i, j) =
  0 if i > j
  1 + f(i, j - 1) if S[j] has no matching
    opening at or after S[i]
  Otherwise min(
      f(k + 1, j - 1)
      + f(i, k - 1)
    )
    where k ≥ i
      and S[k] is a matching opening parenthetical for S[j]
Sign up to request clarification or add additional context in comments.

Comments

1

If the number of brackets to remove is expected to be small relative to the input size, then a breadth-first search might be an efficient approach.

Here is an implementation in Python so it can be compared with other answers that provided Python code:

def solve(brackets):
    n = len(brackets)
    # The queue represents nodes on one level in the BFS search tree.
    #   It has tuples of (stack, index), where the stack is a linked list
    #   of used indices (having opening brackets), and the index is the next
    #   unprocessed character from the input.
    queue = [ (None, 0) ]
    num_removals = 0
    while queue: # BFS loop
        next_queue = []
        # Iterate all relevant possibilities to 
        #    remove numRemovals brackets before index i
        for stack, i in queue:
            # Perform stack operations starting at character index i:
            while i < n:
                if brackets[i] in "[{(":
                    stack = (stack, i)  # push on stack
                elif stack and brackets[stack[1]] + brackets[i] in ("()","{}","[]"):
                    stack = stack[0]  # pop from stack
                else:
                    break
                i += 1
            else:
                if not stack:
                    return num_removals  # BINGO!
            # We have an opening/closing pair that is not of the same type, 
            #   like "(" and "]". Two options for what to remove:
            if stack:
                next_queue.append( (stack[0], i) )  # discard the opening bracket
            if i < len(brackets):
                next_queue.append( (stack, i + 1) )  # discard the closing bracket
        queue = next_queue
        num_removals += 1

Here is the same in a JavaScript snippet where you can interactively input your brackets:

function solve(brackets) {
    let queue = [ [null, 0] ];
    let numRemovals = 0;
    while (queue.length) { // BFS loop
        const nextQueue = [];
        for (let [stackTop, i] of queue) {
            while (true) {
                if ("[{(".includes(brackets[i])) {
                    stackTop = [stackTop, i]; // push on stack
                } else if (stackTop && ["()","{}","[]"]
                           .includes(brackets[stackTop[1]] + brackets[i])) {
                    stackTop = stackTop[0]; // pop from stack
                } else break;
                i++;
            }
            if (!stackTop && i >= brackets.length) return numRemovals;
            if (stackTop) nextQueue.push([stackTop[0], i]);
            if (i < brackets.length) nextQueue.push([stackTop, i + 1]);
        }
        queue = nextQueue;
        numRemovals++;
    }
}

// I/O management
const [input, output] = document.querySelectorAll("input, span");
input.addEventListener("input", refresh);
function refresh() {
    const brackets = input.value.replace(/[^(){}\[]]/g, ""); // clean input
    const count = solve(brackets);
    output.textContent = count;
}
refresh();
Brackets: <input value="[(])"><br>
Is valid after removal of <span>0</span> brackets. 

1 Comment

I was actually searching to see if you wrote an answer to this question previously, to find a duplicate :)
0

This is the algorithm

def min_removals_to_make_valid(s):
    stack = []
    invalid_count = 0
    pair = {')': '(', ']': '[', '}': '{'}

    for ch in s:
        if ch in "([{":
            stack.append(ch)
        elif ch in ")]}":
            if stack and stack[-1] == pair[ch]:
                stack.pop()
            else:
                invalid_count += 1  # unmatched or mistyped closing bracket

    return invalid_count + len(stack)

6 Comments

This returns 5 for "( [ { ] )". The correct answer is 1.
Unfortunately this does not return the correct results given the test cases.
Could you share me some sample input? Because, it works well on my side
([{]) = 1, ([)] = 2, ([]]) = 1, ([{}]) = 0
Sorry seems like my solution is not working.
Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?
0

This solution uses DFS with memoization (functools.lru_cache in python) to explore all possible ways to remove brackets and find the minimum number of removals to make the string valid.

You know there are 2 behaviors for each character - one is removing and next will be keeping.

In the case of keeping a bracket, we only proceed when it maintains valid structure.

  • We can push opening brackets into a simulated stack.

  • We can pop only if the closing ticket matches the last opening. (fully matched and no need to remove)

DFS is based on recursive methods, and this will track the index and current stack of unmatched bracket openings.

And finally, result is the minimal total of removed characters needed to balance the string.

from functools import lru_cache

def min_removals_to_make_valid(s):
    pairs = {')': '(', ']': '[', '}': '{'}
    opens = set(pairs.values())
    closes = set(pairs.keys())

    @lru_cache(None)
    def dfs(i, stack):
        if i == len(s):
            return len(stack)  # unclosed open brackets

        ch = s[i]
        min_remove = float('inf')

        # Option 1: Remove ch
        min_remove = min(min_remove, 1 + dfs(i + 1, stack))

        # Option 2: Keep ch
        if ch in opens:
            min_remove = min(min_remove, dfs(i + 1, stack + (ch,)))
        elif ch in closes:
            if stack and stack[-1] == pairs[ch]:
                min_remove = min(min_remove, dfs(i + 1, stack[:-1]))
        else:
            # not a bracket
            min_remove = min(min_remove, dfs(i + 1, stack))

        return min_remove

    return dfs(0, ())

#- "([)]" should return 2
# - "([{}])" should return 0
# - "([]])" should return 1
input = "([]])"
print(min_removals_to_make_valid(input))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.