LeetCode 697 - Degree of an Array
The problem asks us to find the shortest contiguous subarray of an array nums that has the same degree as the original array. The degree of an array is defined as the maximum frequency of any element in that array.
Difficulty: 🟢 Easy
Topics: Array, Hash Table
Solution
Problem Understanding
The problem asks us to find the shortest contiguous subarray of an array nums that has the same degree as the original array. The degree of an array is defined as the maximum frequency of any element in that array. In other words, if the element that appears the most appears k times, then k is the degree.
The input nums is a non-empty list of non-negative integers, and the output is a single integer representing the length of the smallest contiguous subarray that contains all occurrences of one of the elements contributing to the array's degree.
The constraints tell us that the array can be fairly large, up to 50,000 elements, and each element value can also be large, up to 49,999. This means we need a solution that is at least linear in time, as brute force approaches checking all possible subarrays would be far too slow. Important edge cases include arrays with all identical elements, arrays where multiple elements have the same maximum frequency, and arrays with degree 1 where every element is unique.
Approaches
A brute-force approach would be to compute the degree of the array, then for each element that occurs as many times as the degree, check all subarrays containing all occurrences of that element and track the smallest length. This approach is correct but would be very slow because generating all subarrays would take O(n²) time and is not feasible for n up to 50,000.
The key insight for an optimal solution is to track the first and last occurrence of each number while computing their frequency in a single pass. Once we know the first and last occurrence of every number, we can compute the smallest contiguous subarray length for elements with maximum frequency by simply taking last_index - first_index + 1. This reduces the problem to a single traversal of the array and a constant-time calculation per unique element, resulting in an O(n) solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Check all subarrays for each element contributing to the degree |
| Optimal | O(n) | O(n) | Track frequency, first index, last index, then compute minimal subarray length |
Algorithm Walkthrough
- Initialize three dictionaries:
freqto store frequency of each number,first_indexto store the first occurrence index, andlast_indexto store the last occurrence index. - Iterate through the array using an index
i. For each elementnum:
- Increment
freq[num]by 1. - If
numis not infirst_index, setfirst_index[num] = i. - Always update
last_index[num] = i.
- Compute the degree of the array as the maximum value in
freq. - Initialize a variable
min_lengthto infinity. Iterate through each number infreq:
- If
freq[num]equals the degree, calculate the length of the subarray containing all occurrences aslast_index[num] - first_index[num] + 1. - Update
min_lengthif this length is smaller than the currentmin_length.
- Return
min_length.
This algorithm works because the subarray that captures all occurrences of a number contributing to the degree must start at its first occurrence and end at its last occurrence. By considering only these indices, we ensure the subarray contains all instances of that number and is minimal.
Python Solution
from typing import List
class Solution:
def findShortestSubArray(self, nums: List[int]) -> int:
freq = {}
first_index = {}
last_index = {}
for i, num in enumerate(nums):
if num not in first_index:
first_index[num] = i
last_index[num] = i
freq[num] = freq.get(num, 0) + 1
degree = max(freq.values())
min_length = float('inf')
for num in freq:
if freq[num] == degree:
min_length = min(min_length, last_index[num] - first_index[num] + 1)
return min_length
The Python implementation follows the algorithm directly. We track first and last occurrences and frequency in a single pass through the array. After computing the degree, we iterate over the frequency dictionary to find the minimal subarray length for elements with maximum frequency.
Go Solution
func findShortestSubArray(nums []int) int {
freq := make(map[int]int)
firstIndex := make(map[int]int)
lastIndex := make(map[int]int)
for i, num := range nums {
if _, exists := firstIndex[num]; !exists {
firstIndex[num] = i
}
lastIndex[num] = i
freq[num]++
}
degree := 0
for _, count := range freq {
if count > degree {
degree = count
}
}
minLength := len(nums)
for num, count := range freq {
if count == degree {
length := lastIndex[num] - firstIndex[num] + 1
if length < minLength {
minLength = length
}
}
}
return minLength
}
In Go, maps are used similarly to Python dictionaries. The key differences are that we explicitly check for the existence of a key before setting the first occurrence and that we initialize minLength to the array length instead of infinity, which is more idiomatic in Go.
Worked Examples
Example 1: [1,2,2,3,1]
| Step | i | num | freq | first_index | last_index |
|---|---|---|---|---|---|
| 1 | 0 | 1 | {1:1} | {1:0} | {1:0} |
| 2 | 1 | 2 | {1:1,2:1} | {1:0,2:1} | {1:0,2:1} |
| 3 | 2 | 2 | {1:1,2:2} | {1:0,2:1} | {1:0,2:2} |
| 4 | 3 | 3 | {1:1,2:2,3:1} | {1:0,2:1,3:3} | {1:0,2:2,3:3} |
| 5 | 4 | 1 | {1:2,2:2,3:1} | {1:0,2:1,3:3} | {1:4,2:2,3:3} |
Degree is 2. For numbers 1 and 2: lengths are 4-0+1=5 and 2-1+1=2. Minimum length is 2.
Example 2: [1,2,2,3,1,4,2]
Degree is 3 for number 2. Subarray length is 6-1+1=6.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass to compute frequency and indices, then iterate through unique numbers |
| Space | O(n) | Dictionaries/maps to store frequency and first/last occurrence of each unique element |
The reasoning is that the number of unique elements is at most n, and all operations per element are constant time.
Test Cases
# test cases
sol = Solution()
# provided examples
assert sol.findShortestSubArray([1,2,2,3,1]) == 2 # minimal subarray [2,2]
assert sol.findShortestSubArray([1,2,2,3,1,4,2]) == 6 # minimal subarray [2,2,3,1,4,2]
# edge cases
assert sol.findShortestSubArray([1]) == 1 # single element
assert sol.findShortestSubArray([1,1,1,1]) == 4 # all same elements
assert sol.findShortestSubArray([1,2,3,4,5]) == 1 # all unique elements
assert sol.findShortestSubArray([1,2,2,3,1,1,2]) == 6 # multiple elements with same degree
| Test | Why |
|---|---|
[1,2,2,3,1] |
Basic case with two elements contributing to degree |
[1,2,2,3,1,4,2] |
Degree > 2 with subarray spanning middle of array |
[1] |
Single element array |
[1,1,1,1] |
All elements identical, subarray is full array |
[1,2,3,4,5] |
Degree 1, minimal subarray length is 1 |
[1,2,2,3,1,1,2] |
Multiple elements share max frequency, pick shortest subarray |
Edge Cases
A critical edge case is when the array has all identical elements, such as [1,1,1,1]. In this case, the degree is equal to the length of the array, and the minimal subarray containing all occurrences is the entire array. The implementation handles this correctly because it computes last_index - first_index + 1, which equals the array length.
Another edge case is when multiple elements share the maximum frequency. For example, `[1