LeetCode 2799: Count Complete Subarrays in an Array
The problem "Count Complete Subarrays in an Array" (LeetCode 2799) requires us to count how many subarrays contain all distinct elements present in the original array. A subarray is a contiguous part of the array.
Problem Statement
Given an array of positive integers nums
, return the number of complete subarrays. A subarray is complete if it contains all the distinct elements present in the entire array.
Constraints
1 ≤ nums.length ≤ 100
1 ≤ nums[i] ≤ 2000
Problem Link
Example 1
Input: nums = [1, 3, 1, 2, 2]
Output: 4
Explanation:
The distinct elements in the whole array are {1, 2, 3}.
The complete subarrays are:
[1, 3, 1, 2]
[1, 3, 1, 2, 2]
[3, 1, 2]
[3, 1, 2, 2]
Approach 1: Brute Force (Check All Subarrays)
The brute force approach checks every possible subarray to see if it contains all distinct elements from the original array.
Algorithm Steps
- Count the distinct elements in the entire array
- Initialize a counter for complete subarrays
- Iterate through all possible starting indices
- For each starting index, iterate through all possible ending indices
- For each subarray, count the distinct elements
- If the count matches the total distinct elements, increment our counter
- Return the total count
Complexity Analysis
Time Complexity | Space Complexity |
---|---|
O(n²) | O(n) |
Implementation
class Solution {
public:
int countCompleteSubarrays(vector<int>& nums) {
int count = 0;
unordered_map<int, int> mp;
for (int num : nums) {
mp[num]++;
}
int totalDistinct = mp.size();
for (int i = 0; i < nums.size(); i++) {
unordered_map<int, int> temp;
for (int j = i; j < nums.size(); j++) {
temp[nums[j]]++;
if (temp.size() == totalDistinct)
count++;
}
}
return count;
}
};
class Solution {
public int countCompleteSubarrays(int[] nums) {
int count = 0;
Set<Integer> distinct = new HashSet<>();
for (int num : nums) {
distinct.add(num);
}
int totalDistinct = distinct.size();
for (int i = 0; i < nums.length; i++) {
Set<Integer> current = new HashSet<>();
for (int j = i; j < nums.length; j++) {
current.add(nums[j]);
if (current.size() == totalDistinct) {
count++;
}
}
}
return count;
}
}
class Solution:
def countCompleteSubarrays(self, nums: List[int]) -> int:
count = 0
total_distinct = len(set(nums))
for i in range(len(nums)):
current = set()
for j in range(i, len(nums)):
current.add(nums[j])
if len(current) == total_distinct:
count += 1
return count
Approach 2: Sliding Window (Optimized)
The sliding window approach efficiently counts complete subarrays by maintaining a window that contains all distinct elements and calculating valid subarrays from that window.
Algorithm Steps
- Count the distinct elements in the entire array
- Initialize two pointers (left and right) to represent the window
- Use a hash map to track elements in the current window
- Expand the window by moving the right pointer
- When the window contains all distinct elements:
- All subarrays ending at right pointer from current left are valid
- Add these to the count
- Move left pointer to try smaller windows
- Continue until all elements are processed
Complexity Analysis
Time Complexity | Space Complexity |
---|---|
O(n) | O(n) |
Implementation
class Solution {
public:
int countCompleteSubarrays(vector<int>& nums) {
int n = nums.size();
unordered_set<int> distinct(nums.begin(), nums.end());
int totalDistinct = distinct.size();
unordered_map<int, int> window;
int left = 0, count = 0;
for (int right = 0; right < n; right++) {
window[nums[right]]++;
while (window.size() == totalDistinct) {
count += n - right;
window[nums[left]]--;
if (window[nums[left]] == 0) {
window.erase(nums[left]);
}
left++;
}
}
return count;
}
};
class Solution {
public int countCompleteSubarrays(int[] nums) {
int n = nums.length;
Set<Integer> distinct = new HashSet<>();
for (int num : nums) distinct.add(num);
int totalDistinct = distinct.size();
Map<Integer, Integer> window = new HashMap<>();
int left = 0, count = 0;
for (int right = 0; right < n; right++) {
window.put(nums[right], window.getOrDefault(nums[right], 0) + 1);
while (window.size() == totalDistinct) {
count += n - right;
window.put(nums[left], window.get(nums[left]) - 1);
if (window.get(nums[left]) == 0) {
window.remove(nums[left]);
}
left++;
}
}
return count;
}
}
class Solution:
def countCompleteSubarrays(self, nums: List[int]) -> int:
n = len(nums)
total_distinct = len(set(nums))
window = {}
left = count = 0
for right in range(n):
window[nums[right]] = window.get(nums[right], 0) + 1
while len(window) == total_distinct:
count += n - right
window[nums[left]] -= 1
if window[nums[left]] == 0:
del window[nums[left]]
left += 1
return count
Real-Life Analogy
Imagine you're collecting Pokémon cards. Your goal is to find all sets of continuous cards (subarrays) that include all types of Pokémon you have.
- Brute Force: You check every possible pack manually to see if it has all Pokémon types.
- Sliding Window: You keep adding cards to your hand until you have all Pokémon, then slide your hand forward to explore smaller possible complete sets.
Comparison of Approaches
Approach | Time Complexity | Space Complexity | When to Use |
---|---|---|---|
Brute Force | O(n²) | O(n) | Small inputs or when simplicity is preferred |
Sliding Window | O(n) | O(n) | Larger inputs or when efficiency is critical |
Frequently Asked Questions
1. What exactly is a "complete" subarray?
A complete subarray is a contiguous sequence of elements that contains all distinct elements present in the original array. For example, if the array has elements {1, 2, 3}, then any subarray containing at least one of each is complete.
2. Why does the sliding window approach work better?
The sliding window approach is more efficient because it processes each element at most twice (once when expanding the window, once when contracting it), resulting in O(n) time complexity compared to the brute force O(n²) approach.
3. How does the sliding window count all valid subarrays?
When the window contains all distinct elements, every subarray starting from the current left pointer to the end of the array (from the right pointer) is valid. That's why we add (n - right) to our count when the window contains all distinct elements.
4. What's the space complexity of these solutions?
Both solutions have O(n) space complexity because they need to store the distinct elements. The sliding window approach additionally needs space for the current window's elements.
5. Can this problem be solved with a fixed window size?
No, because complete subarrays can be of varying lengths. A fixed window approach wouldn't work since we need to find all subarrays that happen to contain all distinct elements, regardless of their size.
6. How would you handle negative numbers in the array?
The solutions would work the same way with negative numbers since we're only concerned with distinct values, not their sign. The hash map/set would store negative numbers without issues.
7. What if the array contains duplicate distinct elements?
The solutions still work because we first count the total distinct elements in the entire array, then look for subarrays that contain all of them, regardless of how many times each appears in the subarray.
8. Is there a way to solve this with O(1) space?
No, because we need to store information about the distinct elements, which requires O(n) space in the worst case where all elements are distinct.
9. How does the time complexity change if the array has all unique elements?
In the worst case where all elements are unique, both approaches still maintain their time complexities (O(n²) for brute force, O(n) for sliding window), though the sliding window would effectively check every single-element subarray.
10. What similar problems should I practice?
Recommended problems: 3 (Longest Substring Without Repeating Characters), 76 (Minimum Window Substring), 209 (Minimum Size Subarray Sum), 904 (Fruit Into Baskets), and 930 (Binary Subarrays With Sum).