1111. Maximum Nesting Depth of Two Valid Parentheses Strings
Problem Description
You are given a valid parentheses string (VPS) consisting only of "("
and ")"
characters. A VPS is defined as:
- An empty string, or
- A concatenation of two VPS strings
AB
, or - A string of the form
"(A)"
whereA
is a VPS
The nesting depth of a VPS is defined as:
depth("") = 0
for an empty stringdepth(A + B) = max(depth(A), depth(B))
for concatenation of two VPS stringsdepth("(" + A + ")") = 1 + depth(A)
for a VPS wrapped in parentheses
For example, ""
has depth 0, "()()"
has depth 1, and "()(()())"
has depth 2.
Your task is to split the given VPS seq
into two disjoint subsequences A
and B
such that:
- Both
A
andB
are valid parentheses strings - Every character in
seq
belongs to eitherA
orB
(no character is left out) - The maximum depth between
A
andB
is minimized (i.e.,max(depth(A), depth(B))
is as small as possible)
Return an array of the same length as seq
where:
answer[i] = 0
ifseq[i]
belongs to subsequenceA
answer[i] = 1
ifseq[i]
belongs to subsequenceB
The solution uses a greedy approach by tracking the current balance of parentheses with variable x
. When encountering an opening parenthesis "("
, if x
is odd, it's assigned to one group, otherwise to the other. This strategy ensures that consecutive nested parentheses are distributed between the two groups, effectively minimizing the maximum depth.
Intuition
The key insight is understanding what creates depth in a valid parentheses string. Depth increases when we have nested parentheses, like "(())"
which has depth 2. The innermost ()
contributes to the maximum depth.
To minimize the maximum depth when splitting into two subsequences, we need to distribute the nested parentheses between the two groups as evenly as possible. Think of it like this: if we have deeply nested parentheses all going to one group, that group will have high depth. But if we alternate the assignment of nested levels between the two groups, we can roughly halve the maximum depth.
Consider the string "((()))"
with depth 3. If we assign all parentheses to group A, it would have depth 3. But if we assign alternating levels to different groups:
- Level 1 (outermost) → Group A:
"( )"
- Level 2 (middle) → Group B:
" ( ) "
- Level 3 (innermost) → Group A:
" () "
This way, Group A gets levels 1 and 3, Group B gets level 2, resulting in both groups having depth 2 instead of 3.
The variable x
in the solution tracks our current nesting level. As we encounter an opening "("
, we're going deeper (increment x
), and when we encounter a closing ")"
, we're coming back up (decrement x
). By using x & 1
(checking if x
is odd or even), we alternate the assignment of parentheses at different nesting levels between the two groups.
This greedy strategy ensures that consecutive nesting levels are split between A and B, which optimally minimizes the maximum depth of the resulting subsequences.
Learn more about Stack patterns.
Solution Approach
The implementation uses a greedy algorithm with a simple tracking mechanism to distribute parentheses between two groups.
Algorithm Steps:
-
Initialize the result array: Create an array
ans
of the same length as the input stringseq
, initialized with zeros. This array will store our answer where0
means the character belongs to group A and1
means it belongs to group B. -
Track nesting level: Use a variable
x
initialized to0
to track the current nesting level as we traverse the string. -
Process each character: Iterate through each character in
seq
along with its index:- For opening parenthesis
"("
:- First, check if the current level
x
is odd or even usingx & 1
(bitwise AND with 1) - Assign the parenthesis to group A (value 0) if
x
is even, or group B (value 1) ifx
is odd - Increment
x
to indicate we're going one level deeper
- First, check if the current level
- For closing parenthesis
")"
:- First, decrement
x
since we're closing a level - Then check if the new level
x
is odd or even usingx & 1
- Assign the parenthesis to the corresponding group based on the parity
- First, decrement
- For opening parenthesis
Why this works:
The key insight is that matching parentheses (an opening and its corresponding closing) must be in the same group to maintain validity. By checking the parity of x
at the same nesting level for both opening and closing parentheses, we ensure:
- Matching pairs stay together in the same group
- Adjacent nesting levels alternate between groups
- The depth is distributed as evenly as possible
Example walkthrough with "((()))"
:
- Index 0:
"("
,x=0
(even) → group A, thenx=1
- Index 1:
"("
,x=1
(odd) → group B, thenx=2
- Index 2:
"("
,x=2
(even) → group A, thenx=3
- Index 3:
")"
,x=3
→ decrement tox=2
(even) → group A - Index 4:
")"
,x=2
→ decrement tox=1
(odd) → group B - Index 5:
")"
,x=1
→ decrement tox=0
(even) → group A
Result: [0, 1, 0, 0, 1, 0]
, which splits the string into two valid subsequences with balanced depth.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the string "(()())"
:
Initial Setup:
- Input:
"(()())"
- Initialize
ans = [0, 0, 0, 0, 0, 0]
(all zeros initially) - Initialize
x = 0
(tracking nesting level)
Step-by-step Processing:
Index 0: '('
- Current
x = 0
(even) → assign to group A (ans[0] = 0
) - Increment
x
to 1 - State:
ans = [0, 0, 0, 0, 0, 0]
,x = 1
Index 1: '('
- Current
x = 1
(odd) → assign to group B (ans[1] = 1
) - Increment
x
to 2 - State:
ans = [0, 1, 0, 0, 0, 0]
,x = 2
Index 2: ')'
- Decrement
x
to 1 - Current
x = 1
(odd) → assign to group B (ans[2] = 1
) - State:
ans = [0, 1, 1, 0, 0, 0]
,x = 1
Index 3: '('
- Current
x = 1
(odd) → assign to group B (ans[3] = 1
) - Increment
x
to 2 - State:
ans = [0, 1, 1, 1, 0, 0]
,x = 2
Index 4: ')'
- Decrement
x
to 1 - Current
x = 1
(odd) → assign to group B (ans[4] = 1
) - State:
ans = [0, 1, 1, 1, 1, 0]
,x = 1
Index 5: ')'
- Decrement
x
to 0 - Current
x = 0
(even) → assign to group A (ans[5] = 0
) - State:
ans = [0, 1, 1, 1, 1, 0]
,x = 0
Final Result: [0, 1, 1, 1, 1, 0]
Verification:
- Group A (indices with 0): positions 0 and 5 →
"( )"
→ valid with depth 1 - Group B (indices with 1): positions 1,2,3,4 →
" ()() "
→ valid with depth 1
Both subsequences are valid and have depth 1, which is optimal for this string (original depth was 2).
Solution Implementation
1class Solution:
2 def maxDepthAfterSplit(self, seq: str) -> List[int]:
3 # Initialize result array to store group assignments (0 or 1)
4 result = [0] * len(seq)
5
6 # Track current depth/nesting level of parentheses
7 depth = 0
8
9 # Iterate through each character in the sequence
10 for index, char in enumerate(seq):
11 if char == "(":
12 # For opening parenthesis:
13 # Assign to group 0 if depth is even, group 1 if odd
14 result[index] = depth & 1
15 # Increase depth after opening parenthesis
16 depth += 1
17 else: # char == ")"
18 # For closing parenthesis:
19 # Decrease depth first (matching the corresponding opening)
20 depth -= 1
21 # Assign to same group as its matching opening parenthesis
22 result[index] = depth & 1
23
24 return result
25
1class Solution {
2 public int[] maxDepthAfterSplit(String seq) {
3 int sequenceLength = seq.length();
4 int[] groupAssignment = new int[sequenceLength];
5
6 // Track the current depth/nesting level of parentheses
7 int currentDepth = 0;
8
9 for (int index = 0; index < sequenceLength; index++) {
10 if (seq.charAt(index) == '(') {
11 // For opening parenthesis: assign group based on current depth parity
12 // then increment depth
13 groupAssignment[index] = currentDepth & 1; // 0 if depth is even, 1 if odd
14 currentDepth++;
15 } else {
16 // For closing parenthesis: decrement depth first
17 // then assign group based on new depth parity
18 currentDepth--;
19 groupAssignment[index] = currentDepth & 1; // 0 if depth is even, 1 if odd
20 }
21 }
22
23 return groupAssignment;
24 }
25}
26
1class Solution {
2public:
3 vector<int> maxDepthAfterSplit(string seq) {
4 int sequenceLength = seq.size();
5 vector<int> result(sequenceLength);
6
7 // Track the current depth of parentheses
8 int currentDepth = 0;
9
10 for (int i = 0; i < sequenceLength; ++i) {
11 if (seq[i] == '(') {
12 // For opening parenthesis, assign group based on current depth parity
13 // Then increment depth
14 result[i] = currentDepth & 1;
15 currentDepth++;
16 } else {
17 // For closing parenthesis, first decrement depth
18 // Then assign group based on new depth parity
19 currentDepth--;
20 result[i] = currentDepth & 1;
21 }
22 }
23
24 return result;
25 }
26};
27
1/**
2 * Splits a valid parentheses sequence into two disjoint subsequences A and B
3 * such that A and B are both valid parentheses sequences and max(depth(A), depth(B))
4 * is minimized.
5 *
6 * @param seq - A valid parentheses sequence string containing only '(' and ')'
7 * @returns An array where each element is 0 or 1, indicating which subsequence
8 * the corresponding character belongs to
9 */
10function maxDepthAfterSplit(seq: string): number[] {
11 const sequenceLength: number = seq.length;
12 const result: number[] = new Array(sequenceLength);
13
14 // Track the current depth of parentheses
15 let currentDepth: number = 0;
16
17 for (let index: number = 0; index < sequenceLength; index++) {
18 if (seq[index] === '(') {
19 // Opening parenthesis: assign based on current depth parity
20 // Use bitwise AND with 1 to get the least significant bit (0 or 1)
21 result[index] = currentDepth & 1;
22 // Increment depth after assignment
23 currentDepth++;
24 } else {
25 // Closing parenthesis: decrement depth first
26 currentDepth--;
27 // Assign based on new depth parity
28 result[index] = currentDepth & 1;
29 }
30 }
31
32 return result;
33}
34
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string seq
. The algorithm iterates through each character in the string exactly once using a single for loop with enumerate, performing constant-time operations (comparison, bitwise AND operation & 1
, increment/decrement, and array assignment) for each character.
The space complexity is O(n)
for the output array ans
which stores the result. However, if we exclude the space required for the output (as is common practice when analyzing space complexity for problems that require returning a result), the space complexity is O(1)
. The algorithm only uses a constant amount of extra space: the variable x
to track the depth, and the loop variables i
and c
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Order of Operations for Closing Parentheses
The Problem:
A common mistake is to check the parity of depth
(or x
) before decrementing it when processing a closing parenthesis. This would look like:
if char == ")": result[index] = depth & 1 # WRONG: checking before decrementing depth -= 1
This breaks the algorithm because matching parentheses won't be assigned to the same group. The closing parenthesis would be checking the parity at a different nesting level than its corresponding opening parenthesis.
The Solution:
Always decrement depth
first for closing parentheses, then check the parity:
if char == ")": depth -= 1 # CORRECT: decrement first result[index] = depth & 1 # then check parity
Pitfall 2: Incorrect Order for Opening Parentheses
The Problem:
Similarly, incrementing depth
before checking parity for opening parentheses:
if char == "(": depth += 1 # WRONG: incrementing before checking result[index] = depth & 1
This would cause the opening parenthesis to be assigned based on the wrong nesting level.
The Solution: Check parity first, then increment for opening parentheses:
if char == "(": result[index] = depth & 1 # CORRECT: check first depth += 1 # then increment
Pitfall 3: Using Modulo Instead of Bitwise AND
The Problem:
Using depth % 2
instead of depth & 1
:
result[index] = depth % 2 # Works but less efficient
While this is functionally correct, it's less efficient than bitwise operations.
The Solution: Use bitwise AND for better performance:
result[index] = depth & 1 # More efficient
Example Demonstrating the First Pitfall
Consider the string "(())"
Incorrect approach (checking before modifying depth for closing parentheses):
- Index 0:
"("
, depth=0 → group 0, depth becomes 1 - Index 1:
"("
, depth=1 → group 1, depth becomes 2 - Index 2:
")"
, depth=2 → group 0 (WRONG!), depth becomes 1 - Index 3:
")"
, depth=1 → group 1 (WRONG!), depth becomes 0
Result: [0, 1, 0, 1]
- This splits into "()"
(group 0) and "()"
(group 1), but the second pair is invalid!
Correct approach:
- Index 0:
"("
, depth=0 → group 0, depth becomes 1 - Index 1:
"("
, depth=1 → group 1, depth becomes 2 - Index 2:
")"
, depth becomes 1 → group 1 (matches with index 1) - Index 3:
")"
, depth becomes 0 → group 0 (matches with index 0)
Result: [0, 1, 1, 0]
- This correctly maintains matching pairs.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!