697. Degree of an Array
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:
- First calculating the degree of the entire array using
Counter
to count element frequencies - Tracking the first and last occurrence positions of each element using
left
andright
dictionaries - For each element that appears with maximum frequency (degree), calculating the length of the subarray from its first to last occurrence
- 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.
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:
- We need to include all occurrences of at least one maximum-frequency element to maintain the degree
- Including anything before the first occurrence or after the last occurrence would only make the subarray longer without changing the degree
- 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:
-
Count frequencies and find degree: We use
Counter(nums)
to create a frequency map of all elements. Thencnt.most_common()[0][1]
gives us the degree - the highest frequency among all elements. -
Track first and last positions: We create two dictionaries:
left
: stores the first occurrence index of each elementright
: 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
-
Find minimum subarray length: We iterate through
nums
and for each elementv
:- 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
- Check if its frequency equals the degree:
-
Return result: The variable
ans
maintains the minimum length throughout the process. We initialize it toinf
(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 EvaluatorExample 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
- Index 0, value 1:
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
- Subarray length =
- 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 wheren
is the length of the input array - Finding the most common element using
most_common()[0][1]
takesO(n)
time as it needs to iterate through all unique elements - The first loop to build the
left
andright
dictionaries iterates through alln
elements once, takingO(n)
time - The second loop iterates through all
n
elements innums
, and for each element, it performs constant time operations (dictionary lookups and comparisons), resulting inO(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 mostn
key-value pairs in the worst case (when all elements are unique), requiringO(n)
space - The
left
dictionary stores at mostn
key-value pairs in the worst case, requiringO(n)
space - The
right
dictionary stores at mostn
key-value pairs in the worst case, requiringO(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.
Which data structure is used to implement priority queue?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!