Maximum Nesting Depth of Two Valid Parentheses Strings
MedPrompt
A string is a valid parentheses string (denoted VPS) if and only if it consists of "(" and ")" characters only, and:
- It is the empty string, or
- It can be written as
AB(Aconcatenated withB), whereAandBare VPS's, or - It can be written as
(A), whereAis a VPS.
We can similarly define the nesting depth depth(S) of any VPS S as follows:
depth("") = 0depth(A + B) = max(depth(A), depth(B)), whereAandBare VPS'sdepth("(" + A + ")") = 1 + depth(A), whereAis 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
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves two separate iterations over the input string. The first pass is dedicated to calculating the nesting depth of each parenthesis. These depths are stored in an auxiliary array. The second pass then uses these stored depths to assign each parenthesis to one of the two subsequences, A or B. The assignment is based on the parity (odd or even) of the character's depth, which effectively balances the nesting depth between the two resulting subsequences.
Algorithm
- Create an integer array
depthsof the same size asseqto store the nesting level of each character. - Initialize a variable
currentDepth = 0. - First Pass: Iterate through the input string
seqfrom left to right.- If the character is
'(', we record its depth as thecurrentDepthand then incrementcurrentDepthfor the next level. - If the character is
')', we first decrementcurrentDepth(as this parenthesis closes the current level) and then record its depth.
- If the character is
- Create an
answerarray of the same size asseq. - Second Pass: Iterate through the
depthsarray.- For each character, assign it to group 0 if its depth is even, and group 1 if its depth is odd. This is done by
answer[i] = depths[i] % 2.
- For each character, assign it to group 0 if its depth is even, and group 1 if its depth is odd. This is done by
- Return the
answerarray.
Walkthrough
The core idea is to first determine the nesting level for every parenthesis in the string. A parenthesis's nesting level is defined by how many pairs of parentheses enclose it. We can calculate this by scanning the string and maintaining a depth counter.
Pass 1: Calculate Depths
We iterate through the string seq and use a counter currentDepth. When we encounter an opening parenthesis '(', its depth is the current value of currentDepth, and we then increment the counter. When we see a closing parenthesis ')', it closes the current nesting level, so we first decrement the counter and then assign the new value as its depth. The depths are stored in an array, say depths.
Pass 2: Assign Groups
After computing all depths, we iterate through the depths array. We assign each parenthesis to a group (0 or 1) based on its depth. A simple and effective strategy is to use the modulo operator: group = depth % 2. This assigns parentheses at depths 0, 2, 4, ... to group 0 and those at depths 1, 3, 5, ... to group 1. This distribution ensures that the maximum depth in either subsequence is minimized.
class Solution { public int[] maxDepthAfterSplit(String seq) { int n = seq.length(); int[] depths = new int[n]; int[] answer = new int[n]; int currentDepth = 0; // Pass 1: Calculate depths for each character for (int i = 0; i < n; i++) { if (seq.charAt(i) == '(') { depths[i] = currentDepth; currentDepth++; } else { // ')' currentDepth--; depths[i] = currentDepth; } } // Pass 2: Assign groups based on depth parity for (int i = 0; i < n; i++) { answer[i] = depths[i] % 2; } return answer; }}Complexity
Time
O(N), where N is the length of `seq`. The algorithm iterates through the string twice, so the total time is O(N) + O(N) = O(N).
Space
O(N), where N is the length of `seq`. This is because we use an auxiliary array `depths` of size N, in addition to the `answer` array which is also of size N.
Trade-offs
Pros
The logic is separated into two distinct, easy-to-understand steps: depth calculation and group assignment.
It correctly solves the problem by minimizing the maximum nesting depth.
Cons
Requires two passes over the input string, making it slightly less performant than a single-pass solution.
Uses extra O(N) space for the
depthsarray, which is not optimal.
This is the most efficient approach, solving the problem in a single pass through the input string. It combines the depth calculation and group assignment steps. By maintaining a running counter for the current nesting depth, it can immediately assign each parenthesis to group 0 or 1 based on the depth's parity. This greedy strategy effectively splits the nested layers of parentheses between the two subsequences, thus ensuring that the maximum depth of either subsequence is as small as possible, which is approximately half of the original string's maximum depth.
Algorithm
- Initialize an
answerarray of the same size asseq. - Initialize a
depthcounter to 0. - Iterate through the string
seqwith an indexifrom 0 ton-1. - For each character
catseq[i]:- If
cis an opening parenthesis'(': a. Assignanswer[i]based on the currentdepth's parity:answer[i] = depth % 2. b. Incrementdepth. - If
cis a closing parenthesis')': a. Decrementdepth. b. Assignanswer[i]based on the newdepth's parity:answer[i] = depth % 2.
- If
- Return the
answerarray.
Walkthrough
This approach optimizes the two-pass method by merging the two steps into one. We can calculate the depth and assign the group for each character on the fly.
We iterate through the string seq once, maintaining a depth variable. The key is how we assign the group (0 or 1) based on this depth.
- When we encounter an opening parenthesis
'(', it belongs to the current nesting level,depth. We assign it to a group based ondepth % 2. After the assignment, we incrementdepthbecause we are entering a new, deeper nesting level. - When we encounter a closing parenthesis
')', it signifies the end of a nesting level. So, we first decrementdepth. This')'belongs to the level we just exited, which is now represented by the newdepthvalue. We then assign its group based on this newdepth % 2.
This method ensures that an opening parenthesis and its corresponding closing parenthesis are assigned to the same group, preserving the structure of a Valid Parentheses String for both subsequences A and B.
class Solution { public int[] maxDepthAfterSplit(String seq) { int n = seq.length(); int[] answer = new int[n]; int depth = 0; for (int i = 0; i < n; i++) { char c = seq.charAt(i); if (c == '(') { // Assign based on current depth, then go deeper answer[i] = depth % 2; depth++; } else { // c == ')' // Go up a level, then assign based on the level we are now in depth--; answer[i] = depth % 2; } } return answer; }}Complexity
Time
O(N), where N is the length of the string `seq`, as we only need to iterate through the string once.
Space
O(N) for the output `answer` array. The auxiliary space complexity is O(1) as we only use a single integer variable (`depth`) to keep track of the state.
Trade-offs
Pros
Highly efficient with an optimal time complexity of O(N).
Space-efficient, using only O(1) auxiliary space (if the output array is not considered).
Simple, elegant, and implemented in a single loop.
Cons
The logic of when to update the depth counter relative to the assignment (
depth++after vs.depth--before) can be slightly tricky, but any consistent application of the core idea yields a valid optimal solution.
Solutions
Solution
class Solution {public int[] maxDepthAfterSplit(String seq) { int n = seq.length(); int[] ans = new int[n]; for (int i = 0, x = 0; i < n; ++i) { if (seq.charAt(i) == '(') { ans[i] = x++ & 1; } else { ans[i] = --x & 1; } } return ans; }}class Solution {public: vector<int> maxDepthAfterSplit(string seq) { int n = seq.size(); vector<int> ans(n); for (int i = 0, x = 0; i < n; ++i) { if (seq[i] == '(') { ans[i] = x++ & 1; } else { ans[i] = --x & 1; } } return ans; }};class Solution: def maxDepthAfterSplit(self, seq: str) -> List[int]: ans = [0] * len(seq) x = 0 for i, c in enumerate(seq): if c == "(": ans[i] = x & 1 x += 1 else: x -= 1 ans[i] = x & 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.