Facebook Pixel

697. Degree of an Array

EasyArrayHash Table
Leetcode Link

Problem Description

You are given a non-empty array of non-negative integers called nums. The degree of an array is defined as the maximum frequency of any element in that array (i.e., how many times the most frequent element appears).

Your task is to find the shortest contiguous subarray that has the same degree as the entire array nums.

For example:

  • If nums = [1, 2, 2, 3, 1], the degree is 2 (both 1 and 2 appear twice)
  • The shortest subarray with degree 2 could be [2, 2] with length 2, or [1, 2, 2, 3, 1] with length 5
  • The answer would be 2 since [2, 2] is shorter

The solution works by:

  1. First calculating the degree of the entire array using Counter to count element frequencies
  2. Tracking the first and last occurrence positions of each element using left and right dictionaries
  3. For each element that appears with maximum frequency (degree), calculating the length of the subarray from its first to last occurrence
  4. Returning the minimum length among all such subarrays

The key insight is that any subarray with the same degree as the original array must contain all occurrences of at least one element that appears with maximum frequency. Therefore, the shortest such subarray would span from the first to the last occurrence of one of these maximum-frequency elements.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To understand this problem, let's think about what determines the degree of a subarray. The degree is the maximum frequency of any element, so if we want a subarray to have the same degree as the original array, it must contain at least one element that appears with that maximum frequency.

Here's the key observation: if an element appears k times in the original array (where k is the degree), then any subarray containing all k occurrences of this element will have degree at least k. Since we can't have a degree higher than the original array's degree, such a subarray will have exactly degree k.

This leads us to an important insight: the shortest subarray with the required degree must span from the first occurrence to the last occurrence of some element that has maximum frequency. Why? Because:

  1. We need to include all occurrences of at least one maximum-frequency element to maintain the degree
  2. Including anything before the first occurrence or after the last occurrence would only make the subarray longer without changing the degree
  3. The optimal subarray is simply the tightest "window" that captures all occurrences of one such element

For example, if element x appears 5 times (which is the degree), and these occurrences are at positions [2, 4, 7, 9, 15], then the subarray from index 2 to 15 will have degree 5. We can't make it shorter because we'd lose some occurrences of x, reducing the degree.

Therefore, our strategy becomes: for each element that appears with maximum frequency, calculate the distance from its first to last occurrence, and take the minimum among all such distances. This gives us the shortest possible subarray with the same degree as the original array.

Solution Approach

The implementation uses a systematic approach with hash maps to efficiently track element positions and frequencies:

  1. Count frequencies and find degree: We use Counter(nums) to create a frequency map of all elements. Then cnt.most_common()[0][1] gives us the degree - the highest frequency among all elements.

  2. Track first and last positions: We create two dictionaries:

    • left: stores the first occurrence index of each element
    • right: stores the last occurrence index of each element

    We iterate through the array once with enumerate(nums):

    for i, v in enumerate(nums):
        if v not in left:
            left[v] = i  # Record first occurrence
        right[v] = i     # Always update last occurrence
  3. Find minimum subarray length: We iterate through nums and for each element v:

    • Check if its frequency equals the degree: cnt[v] == degree
    • If yes, calculate the subarray length from first to last occurrence: right[v] - left[v] + 1
    • Keep track of the minimum length found
  4. Return result: The variable ans maintains the minimum length throughout the process. We initialize it to inf (infinity) to ensure any valid subarray length will be smaller.

The time complexity is O(n) where n is the length of the array, as we make three passes through the array (counting, position tracking, and finding minimum). The space complexity is also O(n) for storing the frequency map and position dictionaries.

The elegance of this solution lies in recognizing that we only need to check subarrays defined by elements with maximum frequency, rather than checking all possible subarrays, which would be much less efficient.

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, 3, 1, 4, 2].

Step 1: Count frequencies and find degree

  • Count each element: {1: 2, 2: 3, 3: 1, 4: 1}
  • The degree is 3 (element 2 appears 3 times, which is the maximum)

Step 2: Track first and last positions

  • As we iterate through the array:
    • Index 0, value 1: left[1] = 0, right[1] = 0
    • Index 1, value 2: left[2] = 1, right[2] = 1
    • Index 2, value 2: left[2] stays 1, right[2] = 2
    • Index 3, value 3: left[3] = 3, right[3] = 3
    • Index 4, value 1: left[1] stays 0, right[1] = 4
    • Index 5, value 4: left[4] = 5, right[4] = 5
    • Index 6, value 2: left[2] stays 1, right[2] = 6

Final dictionaries:

  • left = {1: 0, 2: 1, 3: 3, 4: 5}
  • right = {1: 4, 2: 6, 3: 3, 4: 5}

Step 3: Find minimum subarray length

  • Check each element:
    • Element 1: frequency = 2 ≠ degree (3), skip
    • Element 2: frequency = 3 = degree ✓
      • Subarray length = right[2] - left[2] + 1 = 6 - 1 + 1 = 6
      • This represents subarray [2, 2, 3, 1, 4, 2] from index 1 to 6
    • Element 3: frequency = 1 ≠ degree (3), skip
    • Element 4: frequency = 1 ≠ degree (3), skip

Step 4: Return result

  • Only element 2 has the maximum frequency
  • The minimum (and only) valid subarray length is 6
  • The subarray [2, 2, 3, 1, 4, 2] contains all three occurrences of 2, maintaining degree 3

The answer is 6.

Solution Implementation

1class Solution:
2    def findShortestSubArray(self, nums: List[int]) -> int:
3        # Count frequency of each number in the array
4        frequency_map = Counter(nums)
5      
6        # Find the degree (maximum frequency) of the array
7        max_frequency = frequency_map.most_common()[0][1]
8      
9        # Track first and last occurrence index for each number
10        first_occurrence = {}
11        last_occurrence = {}
12      
13        # Iterate through array to record first and last positions
14        for index, value in enumerate(nums):
15            if value not in first_occurrence:
16                first_occurrence[value] = index
17            last_occurrence[value] = index
18      
19        # Initialize minimum length to infinity
20        min_length = float('inf')
21      
22        # Check each number to find shortest subarray with same degree
23        for value in nums:
24            # Only consider numbers that have the maximum frequency
25            if frequency_map[value] == max_frequency:
26                # Calculate subarray length from first to last occurrence
27                subarray_length = last_occurrence[value] - first_occurrence[value] + 1
28                # Update minimum length if current is shorter
29                if min_length > subarray_length:
30                    min_length = subarray_length
31      
32        return min_length
33
1class Solution {
2    public int findShortestSubArray(int[] nums) {
3        // Map to store frequency count of each number
4        Map<Integer, Integer> frequencyMap = new HashMap<>();
5        // Map to store the first occurrence index of each number
6        Map<Integer, Integer> firstIndexMap = new HashMap<>();
7        // Map to store the last occurrence index of each number
8        Map<Integer, Integer> lastIndexMap = new HashMap<>();
9      
10        // Variable to track the maximum frequency (degree of array)
11        int maxFrequency = 0;
12      
13        // Single pass through the array to populate all maps
14        for (int i = 0; i < nums.length; i++) {
15            int currentNum = nums[i];
16          
17            // Update frequency count
18            frequencyMap.put(currentNum, frequencyMap.getOrDefault(currentNum, 0) + 1);
19          
20            // Update maximum frequency
21            maxFrequency = Math.max(maxFrequency, frequencyMap.get(currentNum));
22          
23            // Record first occurrence index if not already recorded
24            if (!firstIndexMap.containsKey(currentNum)) {
25                firstIndexMap.put(currentNum, i);
26            }
27          
28            // Always update last occurrence index
29            lastIndexMap.put(currentNum, i);
30        }
31      
32        // Initialize minimum length to a large value
33        int minLength = Integer.MAX_VALUE;
34      
35        // Check each number to find the shortest subarray with degree equal to array degree
36        for (int num : nums) {
37            // Only consider numbers that have frequency equal to the array degree
38            if (frequencyMap.get(num) == maxFrequency) {
39                // Calculate length of subarray containing all occurrences of this number
40                int subarrayLength = lastIndexMap.get(num) - firstIndexMap.get(num) + 1;
41              
42                // Update minimum length if current subarray is shorter
43                if (subarrayLength < minLength) {
44                    minLength = subarrayLength;
45                }
46            }
47        }
48      
49        return minLength;
50    }
51}
52
1class Solution {
2public:
3    int findShortestSubArray(vector<int>& nums) {
4        // Map to store frequency count of each number
5        unordered_map<int, int> frequency;
6        // Map to store the first occurrence index of each number
7        unordered_map<int, int> firstIndex;
8        // Map to store the last occurrence index of each number
9        unordered_map<int, int> lastIndex;
10      
11        // Find the degree (maximum frequency) of the array
12        int maxFrequency = 0;
13        for (int i = 0; i < nums.size(); ++i) {
14            int num = nums[i];
15          
16            // Update frequency and track maximum frequency (degree)
17            maxFrequency = max(maxFrequency, ++frequency[num]);
18          
19            // Record first occurrence of this number
20            if (firstIndex.find(num) == firstIndex.end()) {
21                firstIndex[num] = i;
22            }
23          
24            // Update last occurrence of this number
25            lastIndex[num] = i;
26        }
27      
28        // Find the shortest subarray with the same degree
29        int minLength = INT_MAX;
30        for (int num : nums) {
31            // Check if this number has frequency equal to the degree
32            if (frequency[num] == maxFrequency) {
33                // Calculate subarray length from first to last occurrence
34                int subarrayLength = lastIndex[num] - firstIndex[num] + 1;
35              
36                // Update minimum length if current subarray is shorter
37                minLength = min(minLength, subarrayLength);
38            }
39        }
40      
41        return minLength;
42    }
43};
44
1function findShortestSubArray(nums: number[]): number {
2    // Map to store frequency count of each number
3    const frequency: Map<number, number> = new Map();
4    // Map to store the first occurrence index of each number
5    const firstIndex: Map<number, number> = new Map();
6    // Map to store the last occurrence index of each number
7    const lastIndex: Map<number, number> = new Map();
8  
9    // Find the degree (maximum frequency) of the array
10    let maxFrequency = 0;
11    for (let i = 0; i < nums.length; i++) {
12        const num = nums[i];
13      
14        // Update frequency and track maximum frequency (degree)
15        const currentFrequency = (frequency.get(num) || 0) + 1;
16        frequency.set(num, currentFrequency);
17        maxFrequency = Math.max(maxFrequency, currentFrequency);
18      
19        // Record first occurrence of this number
20        if (!firstIndex.has(num)) {
21            firstIndex.set(num, i);
22        }
23      
24        // Update last occurrence of this number
25        lastIndex.set(num, i);
26    }
27  
28    // Find the shortest subarray with the same degree
29    let minLength = Number.MAX_SAFE_INTEGER;
30  
31    // Create a set to avoid checking duplicate numbers
32    const uniqueNums = new Set(nums);
33  
34    for (const num of uniqueNums) {
35        // Check if this number has frequency equal to the degree
36        if (frequency.get(num) === maxFrequency) {
37            // Calculate subarray length from first to last occurrence
38            const subarrayLength = lastIndex.get(num)! - firstIndex.get(num)! + 1;
39          
40            // Update minimum length if current subarray is shorter
41            minLength = Math.min(minLength, subarrayLength);
42        }
43    }
44  
45    return minLength;
46}
47

Time and Space Complexity

Time Complexity: O(n)

  • Creating the Counter object takes O(n) time where n is the length of the input array
  • Finding the most common element using most_common()[0][1] takes O(n) time as it needs to iterate through all unique elements
  • The first loop to build the left and right dictionaries iterates through all n elements once, taking O(n) time
  • The second loop iterates through all n elements in nums, and for each element, it performs constant time operations (dictionary lookups and comparisons), resulting in O(n) time
  • Overall time complexity: O(n) + O(n) + O(n) + O(n) = O(n)

Space Complexity: O(n)

  • The Counter object cnt stores at most n key-value pairs in the worst case (when all elements are unique), requiring O(n) space
  • The left dictionary stores at most n key-value pairs in the worst case, requiring O(n) space
  • The right dictionary stores at most n key-value pairs in the worst case, requiring O(n) space
  • Overall space complexity: O(n) + O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Inefficient Duplicate Checking in the Final Loop

The current implementation iterates through the entire nums array in the final loop, potentially checking the same value multiple times. For example, if nums = [1,1,1,2,2,2], we check value 1 three times and value 2 three times, performing redundant calculations.

Solution: Instead of iterating through nums, iterate through unique values only:

# Instead of:
for value in nums:
    if frequency_map[value] == max_frequency:
        # ...

# Use:
for value in frequency_map:
    if frequency_map[value] == max_frequency:
        # ...

2. Handling Edge Cases with Single Element Arrays

While the problem states the array is non-empty, if the array has only one element like nums = [1], the code works but could be optimized with an early return.

Solution: Add an early check:

if len(nums) == 1:
    return 1

3. Multiple Elements with Same Maximum Frequency

A subtle pitfall is not recognizing that multiple different elements can have the same maximum frequency. The code handles this correctly, but developers might mistakenly optimize by breaking early after finding the first element with maximum frequency.

Incorrect optimization:

for value in frequency_map:
    if frequency_map[value] == max_frequency:
        return last_occurrence[value] - first_occurrence[value] + 1
        # Wrong! This returns the first found, not necessarily the shortest

Correct approach: Always check all elements with maximum frequency to find the minimum length.

4. Memory Optimization Opportunity

The code creates both first_occurrence and last_occurrence dictionaries separately, but they could be combined into a single dictionary storing tuples:

Optimized version:

positions = {}
for index, value in enumerate(nums):
    if value not in positions:
        positions[value] = [index, index]
    else:
        positions[value][1] = index

This reduces the number of dictionary lookups and slightly improves memory usage.

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

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More