Since we can just ignore all alphanumeric characters, we will assume the string contains only parentheses from now on. As in the question, there is only one kind of parenthesis, "()".
If we keep removing balanced parentheses until no more balanced parentheses can be removed, all remaining parentheses must look like like "))…)((…(", which are all unbalanced parentheses. This observation suggests that we should find first that turning point, before which we have unbalanced closing parentheses only and after which we have unbalanced opening parentheses only.
Here is the algorithm. In a nutshell, it computes the turning point first. Then it outputs extra closing parenthesis, scanning the string from the start to the right until the turning point. Symmetrically, it outputs extra opening parenthesis, scanning from the end to the left until the turning point.
Let str
be the string as an array of characters, whose size is $n$.
Initialize turning_point=0, maximum_count=0, count=0
. For each i
from 0
to n-1
do the following.
- If
str[i] = ')'
, add 1 to count
; otherwise, subtract 1.
- If
count > maximum_count
, set turning_point=i
and maximum_count=count
.
Now turning_point
is the index of the turning point.
Reset maximum_count=0, count=0
. For each i
from 0
to turning_point
do the following.
- If
str[i] = ')'
, add 1 to count
; otherwise, subtract 1.
- If
count > maximum_count
, set maximum_count = count
. Output i
as the index of an unbalanced closing parenthesis.
Reset maximum_count=0, count=0
. For each i
from n-1
to turning_point+1
downwards do the following.
- If
str[j] = '('
, add 1 to count
; otherwise, subtract 1.
- If
count > maximum_count
, set maximum_count = count
. Output i
as the index of an unbalanced opening parenthesis.
It is clear that the algorithm runs in $O(n)$ time and $O(1)$ auxiliary memory and $O(u)$ output memory, where $u$ is the number of unbalanced parentheses.
If we analyzed the algorithm above, we will see that, in fact, we do not need to find and use the turning point at all. The nice observation that all unbalanced closing parentheses happens before all unbalanced opening parentheses can be ignored although interesting.
Just hit "run" to see several test results.
Exercise 1. Show that the above algorithm will output a set of parentheses with the least cardinality such that the remaining parentheses are balanced.
Problem 1. Can we generalize the algorithm to the case when the string contains two kinds of parentheses such as "()[]"? We have to determine how to recognize and treat the new situation, the interleaving case, "([)]".