Facebook Pixel

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:

  1. Both parts have the same dominant element
  2. 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 is 2 (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:

  1. First finding the dominant element x and its total count in the entire array
  2. Iterating through the array while tracking how many times x appears in the left part
  3. At each position, checking if x is dominant in both the left part (appears > half) and the right part (appears > half)
  4. Returning the first valid split index found, or -1 if none exists
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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, with cur occurrences of x
  • Right part has n - i elements total, with cnt - cur occurrences of x

For x to be dominant:

  • In the left part: cur > i/2, which means cur * 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:

  1. 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 element x and its count cnt
    • Since the problem guarantees a dominant element exists, x is that element
  2. Initialize tracking variable:

    cur = 0
    • cur tracks the count of dominant element x in the left part as we iterate
  3. Iterate through potential split points:

    for i, v in enumerate(nums, 1):
    • enumerate(nums, 1) starts the index from 1, so i represents the size of the left part
    • v is the current element value
    • The actual split index would be i - 1 (since we're counting from 1)
  4. Update the count for left part:

    if v == x:
        cur += 1
    • Increment cur whenever we encounter the dominant element
  5. 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 if x appears more than half the time in left part
    • Right part condition: (cnt - cur) * 2 > len(nums) - i checks if x 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)
  6. 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 Evaluator

Example 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 count cnt = 5
  • Initialize cur = 0 to track 2s 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 to x)
  • cur = 0 (no 2s in left part)
  • Left part: [1] has 1 element, 0 occurrences of 2
  • Right part: [2,2,2,2,1,2] has 6 elements, 5 occurrences of 2
  • Check: 0 * 2 > 1? No (0 > 1 is false)
  • Not valid, continue

Position i=2 (split at index 1):

  • Current element: nums[1] = 2 (equals x)
  • cur = 1 (one 2 in left part)
  • Left part: [1,2] has 2 elements, 1 occurrence of 2
  • Right part: [2,2,2,1,2] has 5 elements, 4 occurrences of 2
  • Check: 1 * 2 > 2? No (2 > 2 is false)
  • Not valid, continue

Position i=3 (split at index 2):

  • Current element: nums[2] = 2 (equals x)
  • cur = 2 (two 2s in left part)
  • Left part: [1,2,2] has 3 elements, 2 occurrences of 2
  • Right part: [2,2,1,2] has 4 elements, 3 occurrences of 2
  • 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 element 2 (appears 2/3 times)
  • Right part [2,2,1,2] has dominant element 2 (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] takes O(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 takes O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More