3480. Maximize Subarrays After Removing One Conflicting Pair
Problem Description
You have an integer n
that represents an array nums = [1, 2, 3, ..., n]
containing numbers from 1 to n in order.
You're also given a 2D array conflictingPairs
where each conflictingPairs[i] = [a, b]
indicates that elements a
and b
form a conflicting pair.
Your task is to:
- Remove exactly one element from
conflictingPairs
(delete one conflicting pair) - After removal, count all possible non-empty subarrays of
nums
that are valid
A subarray is considered valid if it doesn't contain both elements from any remaining conflicting pair. In other words, for each remaining pair [a, b]
, a valid subarray cannot have both a
and b
present at the same time.
The goal is to find which single conflicting pair to remove such that the number of valid subarrays is maximized, and return this maximum count.
Example to clarify:
- If
n = 3
, thennums = [1, 2, 3]
- If
conflictingPairs = [[1, 3], [2, 3]]
- If we remove
[1, 3]
, only[2, 3]
remains as a conflict - Valid subarrays would be:
[1]
,[2]
,[3]
,[1, 2]
(since 1 and 2 don't conflict after removing [1,3]) - Invalid subarrays would be:
[2, 3]
,[1, 2, 3]
(both contain 2 and 3 which still conflict)
The function should return the maximum possible number of valid subarrays after optimally choosing which conflicting pair to remove.
Intuition
Let's think about how conflicting pairs restrict our subarrays. For any subarray starting at position a
, it can extend rightward until it hits a position b
where (a, b)
forms a conflicting pair. The key insight is that for each starting position, we need to find the earliest (minimum) position where a conflict occurs.
Consider iterating through starting positions from right to left (from n
down to 1
). For each starting position a
, we need to track all positions b > a
that conflict with a
. The rightmost valid ending position for subarrays starting at a
is one less than the minimum such b
.
Now, what happens when we remove one conflicting pair? If we remove a pair that contains this minimum b
value (let's call it b1
), then the new limit becomes the second minimum value (call it b2
). This gives us additional valid subarrays.
The clever observation is that we don't need to try removing every possible pair individually. Instead, we can:
- Calculate the base count of valid subarrays assuming no pairs are removed (using the minimum
b
values) - Track how much benefit we'd get by removing pairs that involve each minimum
b1
For each starting position a
, we maintain:
b1
: the minimum conflicting position (this determines our base count)b2
: the second minimum conflicting position (this determines the benefit if we remove a pair involvingb1
)
The benefit of removing a pair involving b1
is b2 - b1
additional subarrays. We accumulate these potential benefits in a counter array cnt[b1]
.
Finally, the answer is the base count plus the maximum benefit we can get from removing one pair (which is the maximum value in our cnt
array).
This approach is efficient because instead of trying all possible removals, we identify which removal would give us the maximum benefit by tracking the gap between the first and second minimum conflicting positions.
Learn more about Segment Tree and Prefix Sum patterns.
Solution Approach
The implementation follows the intuition by processing conflicting pairs and tracking minimum values efficiently:
1. Data Structure Setup:
- Create an adjacency list
g
whereg[a]
stores all valuesb
that conflict witha
(wherea < b
) - Normalize pairs so that the smaller value is always first: if
a > b
, swap them - Initialize a counter array
cnt
of sizen + 2
to track potential benefits
2. Main Algorithm:
Process starting positions from right to left (n
down to 1
):
- Maintain two variables:
b1
: minimum conflicting position (initiallyn + 1
)b2
: second minimum conflicting position (initiallyn + 1
)
For each starting position a
:
-
Update minimums: Iterate through all
b
values ing[a]
(positions that conflict witha
)- If
b < b1
: Update both values -b2 = b1
,b1 = b
- Else if
b < b2
: Update onlyb2 = b
- If
-
Calculate contributions:
- Base contribution:
ans += b1 - a
(number of valid subarrays starting ata
without removing any pair) - Potential benefit:
cnt[b1] += b2 - b1
(additional subarrays if we remove a pair involvingb1
)
- Base contribution:
3. Find Maximum Benefit:
- Track the maximum value in
cnt
array asadd
- This represents the maximum additional subarrays we can get by removing one conflicting pair
4. Return Result:
- Final answer =
ans + add
- Where
ans
is the base count andadd
is the maximum benefit from removal
Key Pattern: The solution uses a greedy approach combined with dynamic tracking of minimum values. By processing positions in reverse order, we can efficiently maintain the two smallest conflicting positions for each starting point without recomputing them repeatedly.
Time Complexity: O(n + m)
where m
is the number of conflicting pairs
Space Complexity: O(n + m)
for storing the adjacency list and counter array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with n = 4
and conflictingPairs = [[1, 3], [2, 4], [1, 4]]
.
This means nums = [1, 2, 3, 4]
and we have three conflicting pairs.
Step 1: Build adjacency list After normalizing pairs (smaller value first), we get:
g[1] = [3, 4]
(1 conflicts with 3 and 4)g[2] = [4]
(2 conflicts with 4)g[3] = []
(3 has no conflicts with higher numbers)g[4] = []
(4 is the last element)
Step 2: Process each starting position from right to left
Initialize: ans = 0
, cnt = [0, 0, 0, 0, 0, 0]
(size n+2)
Starting at position 4:
b1 = 5
,b2 = 5
(initialized to n+1)- No conflicts in
g[4]
- Base contribution:
ans += 5 - 4 = 1
(subarray [4]) - Benefit if we remove pair with b1=5:
cnt[5] += 5 - 5 = 0
- Running
ans = 1
Starting at position 3:
b1 = 5
,b2 = 5
- No conflicts in
g[3]
- Base contribution:
ans += 5 - 3 = 2
(subarrays [3] and [3,4]) - Benefit:
cnt[5] += 5 - 5 = 0
- Running
ans = 3
Starting at position 2:
b1 = 5
,b2 = 5
- Process conflict with 4:
b1 = 4
,b2 = 5
- Base contribution:
ans += 4 - 2 = 2
(subarrays [2] and [2,3]) - Benefit if we remove pair with b1=4:
cnt[4] += 5 - 4 = 1
- Running
ans = 5
Starting at position 1:
b1 = 5
,b2 = 5
- Process conflict with 3:
b1 = 3
,b2 = 5
- Process conflict with 4: Since 4 > 3 but 4 < 5, update
b2 = 4
- Final:
b1 = 3
,b2 = 4
- Base contribution:
ans += 3 - 1 = 2
(subarrays [1] and [1,2]) - Benefit if we remove pair with b1=3:
cnt[3] += 4 - 3 = 1
- Running
ans = 7
Step 3: Find maximum benefit
cnt = [0, 0, 0, 1, 1, 0]
- Maximum value in
cnt
is 1
Step 4: Return result
- Final answer =
7 + 1 = 8
Verification: The 8 valid subarrays after optimal removal are:
- If we remove [1,3]: [1], [2], [3], [4], [1,2], [2,3], [3,4], [1,2,3]
- If we remove [2,4]: [1], [2], [3], [4], [1,2], [2,3], [3,4], [2,3,4]
Both removals give us 8 valid subarrays, which matches our calculated answer.
Solution Implementation
1class Solution:
2 def maxSubarrays(self, n: int, conflictingPairs: List[List[int]]) -> int:
3 # Build adjacency list where graph[i] contains all nodes j where i < j and (i,j) is a conflicting pair
4 graph = [[] for _ in range(n + 1)]
5 for node_a, node_b in conflictingPairs:
6 # Ensure smaller index comes first
7 if node_a > node_b:
8 node_a, node_b = node_b, node_a
9 graph[node_a].append(node_b)
10
11 # Count array to track potential improvements at each position
12 count = [0] * (n + 2)
13
14 # Initialize result and maximum additional value
15 result = 0
16 max_additional = 0
17
18 # Track the two smallest conflicting nodes for current position
19 smallest_conflict = n + 1 # First smallest conflicting node
20 second_smallest_conflict = n + 1 # Second smallest conflicting node
21
22 # Process nodes in reverse order (from n to 1)
23 for current_node in range(n, 0, -1):
24 # Update smallest and second smallest conflicting nodes
25 for conflict_node in graph[current_node]:
26 if conflict_node < smallest_conflict:
27 # New smallest found, shift previous smallest to second
28 second_smallest_conflict = smallest_conflict
29 smallest_conflict = conflict_node
30 elif conflict_node < second_smallest_conflict:
31 # Update second smallest
32 second_smallest_conflict = conflict_node
33
34 # Add contribution of current node to result
35 result += smallest_conflict - current_node
36
37 # Update count for potential improvement at smallest_conflict position
38 count[smallest_conflict] += second_smallest_conflict - smallest_conflict
39
40 # Track maximum potential improvement
41 max_additional = max(max_additional, count[smallest_conflict])
42
43 # Add the maximum improvement to final result
44 result += max_additional
45
46 return result
47
1class Solution {
2 public long maxSubarrays(int n, int[][] conflictingPairs) {
3 // Create adjacency list to store conflicts
4 // For each element i, store all elements j where j > i and they conflict
5 List<Integer>[] adjacencyList = new List[n + 1];
6 Arrays.setAll(adjacencyList, index -> new ArrayList<>());
7
8 // Build the conflict graph
9 // Ensure smaller index points to larger index
10 for (int[] pair : conflictingPairs) {
11 int smaller = pair[0];
12 int larger = pair[1];
13
14 // Swap if needed to maintain smaller -> larger relationship
15 if (smaller > larger) {
16 int temp = smaller;
17 smaller = larger;
18 larger = temp;
19 }
20 adjacencyList[smaller].add(larger);
21 }
22
23 // Array to count potential additional subarrays ending at each position
24 long[] additionalCount = new long[n + 2];
25 long totalSubarrays = 0;
26 long maxAdditional = 0;
27
28 // Track the two smallest conflicting positions for current element
29 int firstBoundary = n + 1; // Smallest conflicting position
30 int secondBoundary = n + 1; // Second smallest conflicting position
31
32 // Process elements from right to left
33 for (int currentElement = n; currentElement > 0; --currentElement) {
34 // Update boundaries based on conflicts with current element
35 for (int conflictingElement : adjacencyList[currentElement]) {
36 if (conflictingElement < firstBoundary) {
37 // New smallest boundary found
38 secondBoundary = firstBoundary;
39 firstBoundary = conflictingElement;
40 } else if (conflictingElement < secondBoundary) {
41 // New second smallest boundary found
42 secondBoundary = conflictingElement;
43 }
44 }
45
46 // Count subarrays starting at currentElement and ending before firstBoundary
47 totalSubarrays += firstBoundary - currentElement;
48
49 // Track potential additional subarrays that could be formed
50 // if we extend beyond firstBoundary up to secondBoundary
51 additionalCount[firstBoundary] += secondBoundary - firstBoundary;
52
53 // Keep track of maximum additional subarrays possible
54 maxAdditional = Math.max(maxAdditional, additionalCount[firstBoundary]);
55 }
56
57 // Add the maximum possible additional subarrays to the total
58 totalSubarrays += maxAdditional;
59
60 return totalSubarrays;
61 }
62}
63
1class Solution {
2public:
3 long long maxSubarrays(int n, vector<vector<int>>& conflictingPairs) {
4 // Build adjacency list where graph[i] contains all elements that conflict with i
5 // Only store conflicts where i < j to avoid duplication
6 vector<vector<int>> adjacencyList(n + 1);
7
8 for (auto& pair : conflictingPairs) {
9 int firstElement = pair[0];
10 int secondElement = pair[1];
11
12 // Ensure firstElement < secondElement for consistent storage
13 if (firstElement > secondElement) {
14 swap(firstElement, secondElement);
15 }
16
17 // Store the conflict: firstElement conflicts with secondElement
18 adjacencyList[firstElement].push_back(secondElement);
19 }
20
21 // Array to count potential additional subarrays at each position
22 vector<long long> additionalCount(n + 2, 0);
23
24 // Total number of valid subarrays
25 long long totalSubarrays = 0;
26
27 // Maximum additional subarrays we can get
28 long long maxAdditional = 0;
29
30 // Track the two smallest conflicting elements greater than current element
31 // Initialize to n+1 (beyond valid range) indicating no conflicts
32 int smallestConflict = n + 1;
33 int secondSmallestConflict = n + 1;
34
35 // Process elements from largest to smallest
36 for (int currentElement = n; currentElement > 0; --currentElement) {
37 // Update the two smallest conflicting elements based on current element's conflicts
38 for (int conflictingElement : adjacencyList[currentElement]) {
39 if (conflictingElement < smallestConflict) {
40 // New smallest found, shift previous smallest to second
41 secondSmallestConflict = smallestConflict;
42 smallestConflict = conflictingElement;
43 } else if (conflictingElement < secondSmallestConflict) {
44 // Update second smallest
45 secondSmallestConflict = conflictingElement;
46 }
47 }
48
49 // Count subarrays starting at currentElement and ending before smallestConflict
50 totalSubarrays += smallestConflict - currentElement;
51
52 // Track potential additional subarrays between smallestConflict and secondSmallestConflict
53 additionalCount[smallestConflict] += secondSmallestConflict - smallestConflict;
54
55 // Keep track of the maximum additional subarrays we can obtain
56 maxAdditional = max(maxAdditional, additionalCount[smallestConflict]);
57 }
58
59 // Add the maximum possible additional subarrays to the total
60 totalSubarrays += maxAdditional;
61
62 return totalSubarrays;
63 }
64};
65
1/**
2 * Calculates the maximum number of subarrays that can be formed from n elements
3 * given conflicting pairs that cannot be in the same subarray
4 *
5 * @param n - The number of elements (1 to n)
6 * @param conflictingPairs - Array of pairs [a, b] where a and b cannot be in the same subarray
7 * @returns The maximum number of subarrays
8 */
9function maxSubarrays(n: number, conflictingPairs: number[][]): number {
10 // Build adjacency list for conflicts (store only b > a to avoid duplicates)
11 const conflictGraph: number[][] = Array.from({ length: n + 1 }, () => []);
12
13 for (let [elementA, elementB] of conflictingPairs) {
14 // Ensure elementA is smaller than elementB for consistent processing
15 if (elementA > elementB) {
16 [elementA, elementB] = [elementB, elementA];
17 }
18 conflictGraph[elementA].push(elementB);
19 }
20
21 // Track potential additional subarrays at each boundary position
22 const boundaryCount: number[] = Array(n + 2).fill(0);
23
24 // Initialize result and maximum additional subarrays
25 let totalSubarrays: number = 0;
26 let maxAdditionalSubarrays: number = 0;
27
28 // Track the two smallest conflicting elements greater than current element
29 let firstBoundary: number = n + 1;
30 let secondBoundary: number = n + 1;
31
32 // Process elements from largest to smallest
33 for (let currentElement: number = n; currentElement > 0; currentElement--) {
34 // Update boundaries based on conflicts with current element
35 for (const conflictingElement of conflictGraph[currentElement]) {
36 if (conflictingElement < firstBoundary) {
37 // New smallest boundary found, shift existing boundaries
38 secondBoundary = firstBoundary;
39 firstBoundary = conflictingElement;
40 } else if (conflictingElement < secondBoundary) {
41 // New second smallest boundary found
42 secondBoundary = conflictingElement;
43 }
44 }
45
46 // Add subarrays that can be formed from current element to first boundary
47 totalSubarrays += firstBoundary - currentElement;
48
49 // Track potential additional subarrays between boundaries
50 boundaryCount[firstBoundary] += secondBoundary - firstBoundary;
51
52 // Update maximum additional subarrays possible
53 maxAdditionalSubarrays = Math.max(maxAdditionalSubarrays, boundaryCount[firstBoundary]);
54 }
55
56 // Add the maximum additional subarrays to the total
57 totalSubarrays += maxAdditionalSubarrays;
58
59 return totalSubarrays;
60}
61
Time and Space Complexity
The time complexity is O(n + m)
, where n
is the input parameter n and m
is the number of conflicting pairs. The algorithm iterates through all values from n
down to 1 (taking O(n)
time), and for each value a
, it processes all edges in g[a]
. Since each conflicting pair is stored exactly once in the adjacency list, the total time to process all edges across all iterations is O(m)
. Therefore, the overall time complexity is O(n + m)
.
The space complexity is O(n + m)
. The adjacency list g
requires O(n + m)
space (array of size n+1
plus storage for all m
edges), and the cnt
array requires O(n)
space. Since O(n + m)
dominates, the overall space complexity is O(n + m)
.
Note: The reference answer states O(n)
for both complexities where n
is the number of conflicting pairs, which appears to use different notation. In the reference, n
seems to refer to the number of pairs (m
in our analysis), making the reference answer O(m)
in our notation. However, this would be incorrect for the actual implementation since the algorithm must iterate through all values from n
to 1, making the time complexity dependent on both the parameter n
and the number of pairs m
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Tracking of Minimum Conflicting Positions
The Pitfall:
The current implementation has a critical bug in how it tracks the smallest and second smallest conflicting positions. The variables smallest_conflict
and second_smallest_conflict
are initialized once outside the loop and never reset for each position. This causes them to accumulate values from previous iterations, leading to incorrect calculations.
Why This Happens:
When processing position current_node
, we need to find the two smallest positions that conflict with it. However, the code maintains these values across all iterations, meaning conflicts from position n
are still considered when processing position n-1
, n-2
, etc.
The Fix: Reset the tracking variables for each position:
for current_node in range(n, 0, -1):
# Reset for each position - THIS IS THE FIX
smallest_conflict = n + 1
second_smallest_conflict = n + 1
# Update smallest and second smallest conflicting nodes
for conflict_node in graph[current_node]:
if conflict_node < smallest_conflict:
second_smallest_conflict = smallest_conflict
smallest_conflict = conflict_node
elif conflict_node < second_smallest_conflict:
second_smallest_conflict = conflict_node
# Rest of the logic remains the same
2. Misunderstanding the Problem's Counting Logic
The Pitfall:
Developers might incorrectly assume that removing a conflicting pair [a, b]
only affects subarrays containing both a
and b
. The actual impact is more nuanced - it affects all subarrays starting from any position up to a
that would extend to or past b
.
Correct Understanding:
- When we remove a pair involving position
b1
, we extend the valid range from[current_node, b1-1]
to[current_node, b2-1]
- This gives us
b2 - b1
additional valid subarrays starting atcurrent_node
- The
count[b1]
accumulates all such benefits across different starting positions
3. Edge Case: No Conflicts for a Position
The Pitfall:
If a position has no conflicts (empty graph[current_node]
), the smallest_conflict remains as n + 1
, which is correct. However, developers might try to "optimize" by skipping such positions, which would miss counting valid subarrays.
Why It Matters:
Even positions with no conflicts contribute to the answer. A position with no conflicts can form subarrays all the way to position n
, contributing (n + 1) - current_node
valid subarrays.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!