2780. Minimum Index of a Valid Split
Problem Description
You are given an integer array nums
of length n
that contains one dominant element. A dominant element is a value that appears more than half the time in the array.
Your task is to find the minimum index where you can split the array into two parts such that:
- Both parts have the same dominant element
- The split must be valid (you can't create empty subarrays)
When you split at index i
:
- The left part is
nums[0, ..., i]
(inclusive) - The right part is
nums[i + 1, ..., n - 1]
(inclusive)
For the split to be valid:
0 <= i < n - 1
(ensures neither part is empty)- Both the left and right parts must have the same dominant element
Return the minimum index i
where such a valid split can be made. If no valid split exists, return -1
.
Example understanding:
- If
nums = [2, 2, 2, 1, 2]
, the dominant element is2
(appears 4 times out of 5) - If we split at index 1: left =
[2, 2]
(dominant: 2), right =[2, 1, 2]
(dominant: 2) - Both parts have the same dominant element
2
, so this is a valid split
The solution works by:
- First finding the dominant element
x
and its total count in the entire array - Iterating through the array while tracking how many times
x
appears in the left part - At each position, checking if
x
is dominant in both the left part (appears > half) and the right part (appears > half) - Returning the first valid split index found, or
-1
if none exists
Intuition
Since the original array has a dominant element, this element appears more than n/2
times. The key insight is that if we can split the array such that both parts have the same dominant element, then this dominant element must be the same as the original array's dominant element.
Why? Consider any other element that's not the original dominant. It appears less than n/2
times total. For it to be dominant in both parts after a split, it would need to appear more than half the time in each part. But if an element appears more than half in the left part AND more than half in the right part, then it must appear more than half in the total array - which contradicts our assumption that it's not the original dominant element.
So we only need to check if the original dominant element x
remains dominant in both parts after each potential split.
As we iterate through the array from left to right, we can maintain a running count of how many times x
appears in the left part. At position i
:
- Left part has
i
elements total, withcur
occurrences ofx
- Right part has
n - i
elements total, withcnt - cur
occurrences ofx
For x
to be dominant:
- In the left part:
cur > i/2
, which meanscur * 2 > i
- In the right part:
(cnt - cur) > (n - i)/2
, which means(cnt - cur) * 2 > n - i
We check these conditions at each position and return the first valid split index. Since we're iterating from left to right, we automatically get the minimum index.
The beauty of this approach is that we don't need to recalculate everything for each split - we just update our running count incrementally, making the solution run in O(n)
time.
Learn more about Sorting patterns.
Solution Approach
The implementation follows a greedy approach with a single pass through the array:
-
Find the dominant element and its frequency:
x, cnt = Counter(nums).most_common(1)[0]
- Use
Counter
to count occurrences of each element most_common(1)[0]
returns the most frequent elementx
and its countcnt
- Since the problem guarantees a dominant element exists,
x
is that element
- Use
-
Initialize tracking variable:
cur = 0
cur
tracks the count of dominant elementx
in the left part as we iterate
-
Iterate through potential split points:
for i, v in enumerate(nums, 1):
enumerate(nums, 1)
starts the index from 1, soi
represents the size of the left partv
is the current element value- The actual split index would be
i - 1
(since we're counting from 1)
-
Update the count for left part:
if v == x: cur += 1
- Increment
cur
whenever we encounter the dominant element
- Increment
-
Check if current split is valid:
if cur * 2 > i and (cnt - cur) * 2 > len(nums) - i: return i - 1
- Left part condition:
cur * 2 > i
checks ifx
appears more than half the time in left part - Right part condition:
(cnt - cur) * 2 > len(nums) - i
checks ifx
appears more than half the time in right part - If both conditions are satisfied, return
i - 1
as the split index (converting back to 0-indexed)
- Left part condition:
-
Return -1 if no valid split found:
return -1
Time Complexity: O(n)
- one pass to count elements with Counter
, one pass to find the split point
Space Complexity: O(n)
- for the Counter
dictionary storing element frequencies
The algorithm efficiently finds the minimum valid split index by checking each position sequentially and returning immediately upon finding the first valid split.
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 nums = [1, 2, 2, 2, 2, 1, 2]
.
Step 1: Find the dominant element
- Count frequencies:
{1: 2, 2: 5}
- Dominant element
x = 2
with countcnt = 5
- Initialize
cur = 0
to track2
s in the left part
Step 2: Iterate through potential split points
Position i=1 (split at index 0):
- Current element:
nums[0] = 1
(not equal tox
) cur = 0
(no2
s in left part)- Left part:
[1]
has 1 element, 0 occurrences of2
- Right part:
[2,2,2,2,1,2]
has 6 elements, 5 occurrences of2
- Check:
0 * 2 > 1
? No (0 > 1 is false) - Not valid, continue
Position i=2 (split at index 1):
- Current element:
nums[1] = 2
(equalsx
) cur = 1
(one2
in left part)- Left part:
[1,2]
has 2 elements, 1 occurrence of2
- Right part:
[2,2,2,1,2]
has 5 elements, 4 occurrences of2
- Check:
1 * 2 > 2
? No (2 > 2 is false) - Not valid, continue
Position i=3 (split at index 2):
- Current element:
nums[2] = 2
(equalsx
) cur = 2
(two2
s in left part)- Left part:
[1,2,2]
has 3 elements, 2 occurrences of2
- Right part:
[2,2,1,2]
has 4 elements, 3 occurrences of2
- Check left:
2 * 2 > 3
? Yes (4 > 3 is true) - Check right:
(5-2) * 2 > 4
? Yes (6 > 4 is true) - Both conditions satisfied! Return
i - 1 = 2
Result: The minimum split index is 2
.
- Left part
[1,2,2]
has dominant element2
(appears 2/3 times) - Right part
[2,2,1,2]
has dominant element2
(appears 3/4 times)
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def minimumIndex(self, nums: List[int]) -> int:
6 # Find the dominant element (most frequent) and its total count
7 dominant_element, total_count = Counter(nums).most_common(1)[0]
8
9 # Track count of dominant element in the left partition
10 left_count = 0
11
12 # Iterate through array positions (1-indexed for easier calculation)
13 for index, value in enumerate(nums, 1):
14 if value == dominant_element:
15 left_count += 1
16
17 # Check if dominant element is dominant in both partitions:
18 # 1. Left partition: left_count > half of left partition size (index)
19 # 2. Right partition: remaining count > half of right partition size
20 left_is_dominant = left_count * 2 > index
21 right_count = total_count - left_count
22 right_size = len(nums) - index
23 right_is_dominant = right_count * 2 > right_size
24
25 if left_is_dominant and right_is_dominant:
26 # Return 0-based index (index - 1 since we're using 1-based enumeration)
27 return index - 1
28
29 # No valid split point found
30 return -1
31
1class Solution {
2 public int minimumIndex(List<Integer> nums) {
3 // Find the dominant element (appears more than n/2 times)
4 int dominantElement = 0;
5 int dominantCount = 0;
6 Map<Integer, Integer> frequencyMap = new HashMap<>();
7
8 // Count frequency of each element and find the dominant one
9 for (int num : nums) {
10 int currentFrequency = frequencyMap.merge(num, 1, Integer::sum);
11 if (dominantCount < currentFrequency) {
12 dominantCount = currentFrequency;
13 dominantElement = num;
14 }
15 }
16
17 // Find the minimum index where both parts have the same dominant element
18 int leftCount = 0;
19 for (int i = 1; i <= nums.size(); i++) {
20 if (nums.get(i - 1) == dominantElement) {
21 leftCount++;
22
23 // Check if dominant element is dominant in both left and right parts
24 // Left part: [0, i-1], size = i, needs > i/2 occurrences
25 // Right part: [i, n-1], size = n-i, needs > (n-i)/2 occurrences
26 int rightCount = dominantCount - leftCount;
27 int rightSize = nums.size() - i;
28
29 if (leftCount * 2 > i && rightCount * 2 > rightSize) {
30 return i - 1;
31 }
32 }
33 }
34
35 return -1;
36 }
37}
38
1class Solution {
2public:
3 int minimumIndex(vector<int>& nums) {
4 // Step 1: Find the dominant element (appears more than n/2 times)
5 int dominantElement = 0;
6 int maxFrequency = 0;
7 unordered_map<int, int> frequencyMap;
8
9 // Count frequency of each element and find the dominant one
10 for (int num : nums) {
11 frequencyMap[num]++;
12 if (frequencyMap[num] > maxFrequency) {
13 maxFrequency = frequencyMap[num];
14 dominantElement = num;
15 }
16 }
17
18 // Step 2: Find the minimum index where split satisfies the condition
19 int leftCount = 0; // Count of dominant element in left subarray
20
21 for (int i = 0; i < nums.size(); i++) {
22 // Update count if current element is the dominant element
23 if (nums[i] == dominantElement) {
24 leftCount++;
25 }
26
27 // Check if dominant element is dominant in both subarrays
28 int leftSize = i + 1;
29 int rightSize = nums.size() - leftSize;
30 int rightCount = maxFrequency - leftCount;
31
32 // Dominant element must appear more than half in both subarrays
33 if (leftCount * 2 > leftSize && rightCount * 2 > rightSize) {
34 return i;
35 }
36 }
37
38 // No valid split found
39 return -1;
40 }
41};
42
1/**
2 * Finds the minimum index where splitting the array creates two subarrays
3 * with the same dominant element (element appearing more than half the time)
4 * @param nums - The input array of numbers
5 * @returns The minimum valid split index, or -1 if no valid split exists
6 */
7function minimumIndex(nums: number[]): number {
8 // Find the dominant element in the entire array
9 let dominantElement: number = 0;
10 let dominantCount: number = 0;
11 const frequencyMap: Map<number, number> = new Map();
12
13 // Count frequency of each element and identify the dominant one
14 for (const value of nums) {
15 const currentFreq = (frequencyMap.get(value) ?? 0) + 1;
16 frequencyMap.set(value, currentFreq);
17
18 // Update dominant element if current element has higher frequency
19 if (currentFreq > dominantCount) {
20 dominantElement = value;
21 dominantCount = currentFreq;
22 }
23 }
24
25 // Track count of dominant element in the left subarray
26 let leftCount: number = 0;
27
28 // Iterate through possible split positions
29 for (let i = 1; i <= nums.length; i++) {
30 // Check if current element is the dominant element
31 if (nums[i - 1] === dominantElement) {
32 leftCount++;
33
34 // Check if both subarrays have the dominant element as their majority
35 const leftLength = i;
36 const rightLength = nums.length - i;
37 const rightCount = dominantCount - leftCount;
38
39 // Both conditions must be satisfied:
40 // 1. Dominant element appears more than half the time in left subarray
41 // 2. Dominant element appears more than half the time in right subarray
42 if (leftCount * 2 > leftLength && rightCount * 2 > rightLength) {
43 return i - 1; // Return 0-indexed position
44 }
45 }
46 }
47
48 // No valid split found
49 return -1;
50}
51
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main parts:
Counter(nums).most_common(1)[0]
takesO(n)
time to count all elements in the array and find the most common one- The for loop iterates through the array once, performing
O(1)
operations in each iteration (checking equality, incrementing counter, and comparing values), which takesO(n)
time
Therefore, the overall time complexity is O(n) + O(n) = O(n)
, where n
is the length of the input array.
Space Complexity: O(n)
The space complexity is dominated by the Counter
object, which in the worst case stores all unique elements from the input array. If all elements are unique, the Counter would store n
key-value pairs, resulting in O(n)
space complexity. The remaining variables (x
, cnt
, cur
, i
, v
) use constant space O(1)
.
Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Understanding of Split Index
A common mistake is misunderstanding what the split index represents. When splitting at index i
, the left part includes element at index i
, while the right part starts from index i+1
.
Pitfall Example:
# WRONG: Confusing split semantics
for i in range(len(nums)):
left_part = nums[:i] # This would exclude nums[i]
right_part = nums[i:] # This would include nums[i]
Solution:
# CORRECT: Proper split understanding
for i in range(len(nums) - 1): # Can't split at last index
left_part = nums[:i+1] # Include nums[i] in left part
right_part = nums[i+1:] # Start from i+1 for right part
2. Off-by-One Error in Enumeration
The code uses enumerate(nums, 1)
which can be confusing. The variable i
represents the size of the left partition, not the actual array index.
Pitfall Example:
# WRONG: Forgetting to adjust for 1-based enumeration
for i, v in enumerate(nums, 1):
if conditions_met:
return i # This returns the size, not the index!
Solution:
# CORRECT: Convert back to 0-based index
for i, v in enumerate(nums, 1):
if conditions_met:
return i - 1 # Subtract 1 to get the actual split index
3. Premature Optimization Breaking Correctness
Some might try to exit early when it's impossible for the dominant element to be dominant in the right partition, but this can lead to missing valid splits.
Pitfall Example:
# WRONG: Early termination that might miss valid splits
for i, v in enumerate(nums, 1):
if v == dominant_element:
left_count += 1
# Premature check - dominant element might still become dominant later
if (total_count - left_count) * 2 <= len(nums) - i:
return -1 # This could miss valid splits!
Solution:
# CORRECT: Check both conditions at each position
for i, v in enumerate(nums, 1):
if v == dominant_element:
left_count += 1
# Only return when BOTH conditions are satisfied
if left_count * 2 > i and (total_count - left_count) * 2 > len(nums) - i:
return i - 1
# Don't exit early - continue checking all positions
4. Edge Case: Array with Only Two Elements
While the code handles this correctly, it's easy to overlook that with only 2 elements, a valid split is impossible unless both elements are the same.
Pitfall Example:
# WRONG: Not considering minimum array constraints
def minimumIndex(nums):
if len(nums) < 2:
return -1 # Unnecessary check - problem guarantees n >= 1
# But forgetting that n=2 rarely has valid splits
Solution:
The current implementation naturally handles this - for nums = [1, 2]
, neither element can be dominant in both parts after splitting, so it correctly returns -1
.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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!