A string is a valid parentheses string (denoted VPS) if and only if it consists of "("
and ")"
characters only, and:
AB
(A
concatenated with B
), where A
and B
are VPS's, or(A)
, where A
is a VPS.We can similarly define the nesting depth depth(S)
of any VPS S
as follows:
depth("") = 0
depth(A + B) = max(depth(A), depth(B))
, where A
and B
are VPS'sdepth("(" + A + ")") = 1 + depth(A)
, where A
is a VPS.For example, ""
, "()()"
, and "()(()())"
are VPS's (with nesting depths 0, 1, and 2), and ")("
and "(()"
are not VPS's.
Given a VPS seq, split it into two disjoint subsequences A
and B
, such that A
and B
are VPS's (and A.length + B.length = seq.length
).
Now choose any such A
and B
such that max(depth(A), depth(B))
is the minimum possible value.
Return an answer
array (of length seq.length
) that encodes such a choice of A
and B
: answer[i] = 0
if seq[i]
is part of A
, else answer[i] = 1
. Note that even though multiple answers may exist, you may return any of them.
Example 1:
Input: seq = "(()())" Output: [0,1,1,1,1,0]
Example 2:
Input: seq = "()(())()" Output: [0,0,0,1,1,0,1,1]
Constraints:
1 <= seq.size <= 10000
This problem asks us to split a valid parentheses string (seq
) into two disjoint subsequences, A
and B
, such that both are valid parentheses strings and the maximum nesting depth of A
and B
is minimized. The solution uses a greedy approach.
Greedy Approach:
The core idea is to assign parentheses to either A
or B
based on a simple rule that aims to balance the nesting depth between the two subsequences. We maintain a counter, x
, representing the balance of parentheses.
Initialization: x
starts at 0. This represents the difference between open and close parentheses encountered so far.
Iteration: The code iterates through the input string seq
.
A
if x
is even, and to B
if x
is odd. Then, we increment x
because we have one more open parenthesis. This ensures that we try to distribute open parentheses relatively equally between A and B.x
). Then we decrement x
.Result: The ans
array stores the assignments (0 for A
, 1 for B
) for each character in seq
.
Time Complexity: O(n), where n is the length of the input string. We iterate through the string once.
Space Complexity: O(1). We only use a few integer variables and an array to store the result (the size of the result array is equal to the length of the input string, making it O(n) in terms of space usage of the output, however, this is generally considered constant if the output size is not considered in the space complexity analysis). The space used for intermediate values is constant, independent of input size.
Code Examples (Python, Java, C++, Go, TypeScript):
The provided code snippets in various languages implement this greedy algorithm directly. They all share the same basic structure:
ans
to store the assignments (0 or 1).x
to 0.x % 2
and increment x
.x % 2
and decrement x
.Each language's specific syntax is used, but the underlying logic remains consistent.
Example Walkthrough (Python):
Let's trace the Python code with seq = "()(())()"
:
| seq[i]
| x
| x & 1
| ans[i]
|
|---|---|---|---|
| '(' | 0 | 0 | 0 |
| ')' | 1 | 1 | 1 |
| '(' | 0 | 0 | 0 |
| '(' | 1 | 1 | 1 |
| ')' | 2 | 0 | 0 |
| ')' | 1 | 1 | 1 |
| '(' | 0 | 0 | 0 |
| ')' | 1 | 1 | 1 |
The resulting ans
is [0, 1, 0, 1, 0, 1, 0, 1]
, which correctly splits the string into A and B with a minimized maximum nesting depth. Note that this slightly different from the example in the problem description. The greedy approach might not always produce the exact same result as other methods, but it guarantees a valid and optimal solution in terms of the maximum nesting depth.