2799. Count Complete Subarrays in an Array
Problem Description
You are given an array nums
consisting of positive integers.
A subarray is considered complete if it contains all the distinct elements that appear in the entire array. In other words, if the original array has k
distinct elements, then a complete subarray must also contain exactly those k
distinct elements (though possibly in different quantities).
Your task is to count how many complete subarrays exist in the given array.
For example, if nums = [1, 3, 1, 2, 2]
, the distinct elements in the entire array are {1, 2, 3}
, so there are 3 distinct elements. A subarray like [3, 1, 2]
would be complete because it contains all 3 distinct elements. However, a subarray like [1, 3, 1]
would not be complete because it only contains 2 distinct elements (1
and 3
), missing element 2
.
A subarray is defined as a contiguous, non-empty sequence of elements from the array.
The goal is to return the total number of complete subarrays.
Intuition
The key insight is that we need to find all subarrays that contain every distinct element from the original array. First, we need to know what our target is - how many distinct elements exist in the entire array? We can find this by converting the array to a set and checking its size, let's call this cnt
.
Once we know we need cnt
distinct elements in our subarray, we can check all possible subarrays. The natural approach is to consider every possible starting position i
for a subarray. For each starting position, we can extend the subarray one element at a time by moving the ending position to the right.
As we extend the subarray from position i
, we maintain a set s
to track which distinct elements we've seen so far in the current subarray. Each time we include a new element, we add it to our set. The moment our set size reaches cnt
, we know we have a complete subarray - it contains all the distinct elements from the original array.
An important observation: once a subarray starting at i
and ending at some position j
becomes complete (has all cnt
distinct elements), extending it further to the right will still keep it complete. However, the solution counts each complete subarray individually, so we increment our answer for each valid ending position.
This leads to a straightforward enumeration strategy: for each starting position, we expand rightward and count how many complete subarrays we can form from that starting point. The sum across all starting positions gives us the total count.
Learn more about Sliding Window patterns.
Solution Approach
The implementation uses a hash table and enumeration strategy to solve this problem efficiently.
Step 1: Count distinct elements in the entire array
cnt = len(set(nums))
We convert the array to a set to get all unique elements, then take its length. This gives us the target number of distinct elements that a complete subarray must have.
Step 2: Initialize variables
ans, n = 0, len(nums)
ans
keeps track of the total count of complete subarraysn
stores the length of the array for convenience
Step 3: Enumerate all possible subarrays
for i in range(n):
s = set()
for x in nums[i:]:
s.add(x)
if len(s) == cnt:
ans += 1
For each starting position i
:
- Create an empty set
s
to track distinct elements in the current subarray - Iterate through elements starting from position
i
to the end of the array - For each element
x
:- Add it to the set
s
(duplicates are automatically handled by the set) - Check if
len(s) == cnt
- this means we have collected all distinct elements - If true, increment the answer counter by 1
- Add it to the set
The inner loop for x in nums[i:]
effectively extends the subarray one element at a time from the starting position i
. Each time we extend, we check if we've formed a complete subarray.
Time Complexity: O(n²)
where n
is the length of the array. We have two nested loops - the outer loop runs n
times, and for each iteration, the inner loop can run up to n
times.
Space Complexity: O(n)
for storing the sets. In the worst case, the set s
can contain all distinct elements from the array.
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, 3, 1, 2, 2]
.
Step 1: Find the target number of distinct elements
- Convert array to set:
{1, 2, 3}
cnt = 3
(we need 3 distinct elements for a complete subarray)
Step 2: Enumerate subarrays starting from each position
Starting at index 0 (i = 0
):
- Initialize empty set
s = {}
- Process
nums[0] = 1
: Add to set →s = {1}
, size = 1 < 3 - Process
nums[1] = 3
: Add to set →s = {1, 3}
, size = 2 < 3 - Process
nums[2] = 1
: Add to set →s = {1, 3}
, size = 2 < 3 (1 already exists) - Process
nums[3] = 2
: Add to set →s = {1, 3, 2}
, size = 3 = cnt → Found complete subarray [1,3,1,2], ans = 1 - Process
nums[4] = 2
: Add to set →s = {1, 3, 2}
, size = 3 = cnt → Found complete subarray [1,3,1,2,2], ans = 2
Starting at index 1 (i = 1
):
- Initialize empty set
s = {}
- Process
nums[1] = 3
: Add to set →s = {3}
, size = 1 < 3 - Process
nums[2] = 1
: Add to set →s = {3, 1}
, size = 2 < 3 - Process
nums[3] = 2
: Add to set →s = {3, 1, 2}
, size = 3 = cnt → Found complete subarray [3,1,2], ans = 3 - Process
nums[4] = 2
: Add to set →s = {3, 1, 2}
, size = 3 = cnt → Found complete subarray [3,1,2,2], ans = 4
Starting at index 2 (i = 2
):
- Initialize empty set
s = {}
- Process
nums[2] = 1
: Add to set →s = {1}
, size = 1 < 3 - Process
nums[3] = 2
: Add to set →s = {1, 2}
, size = 2 < 3 - Process
nums[4] = 2
: Add to set →s = {1, 2}
, size = 2 < 3 (2 already exists) - End reached, no complete subarrays found
Starting at index 3 (i = 3
):
- Initialize empty set
s = {}
- Process
nums[3] = 2
: Add to set →s = {2}
, size = 1 < 3 - Process
nums[4] = 2
: Add to set →s = {2}
, size = 1 < 3 - End reached, no complete subarrays found
Starting at index 4 (i = 4
):
- Initialize empty set
s = {}
- Process
nums[4] = 2
: Add to set →s = {2}
, size = 1 < 3 - End reached, no complete subarrays found
Final Answer: 4 complete subarrays
The complete subarrays found are:
[1, 3, 1, 2]
(indices 0-3)[1, 3, 1, 2, 2]
(indices 0-4)[3, 1, 2]
(indices 1-3)[3, 1, 2, 2]
(indices 1-4)
Solution Implementation
1class Solution:
2 def countCompleteSubarrays(self, nums: List[int]) -> int:
3 # Count total number of distinct elements in the entire array
4 total_distinct_count = len(set(nums))
5
6 # Initialize result counter and get array length
7 result = 0
8 n = len(nums)
9
10 # Iterate through each starting position
11 for start_index in range(n):
12 # Track distinct elements in current subarray
13 distinct_elements = set()
14
15 # Extend subarray from current starting position
16 for element in nums[start_index:]:
17 # Add current element to the set
18 distinct_elements.add(element)
19
20 # If subarray contains all distinct elements from original array
21 if len(distinct_elements) == total_distinct_count:
22 # Increment count of complete subarrays
23 result += 1
24
25 return result
26
1class Solution {
2 public int countCompleteSubarrays(int[] nums) {
3 // First pass: count total number of distinct elements in the array
4 Set<Integer> distinctElements = new HashSet<>();
5 for (int num : nums) {
6 distinctElements.add(num);
7 }
8 int totalDistinctCount = distinctElements.size();
9
10 // Initialize result counter and get array length
11 int completeSubarrayCount = 0;
12 int arrayLength = nums.length;
13
14 // Iterate through all possible starting positions
15 for (int startIndex = 0; startIndex < arrayLength; ++startIndex) {
16 // Clear set to track distinct elements in current subarray
17 distinctElements.clear();
18
19 // Extend subarray from current starting position
20 for (int endIndex = startIndex; endIndex < arrayLength; ++endIndex) {
21 // Add current element to the set
22 distinctElements.add(nums[endIndex]);
23
24 // Check if current subarray contains all distinct elements
25 if (distinctElements.size() == totalDistinctCount) {
26 // Increment count as we found a complete subarray
27 ++completeSubarrayCount;
28 }
29 }
30 }
31
32 return completeSubarrayCount;
33 }
34}
35
1class Solution {
2public:
3 int countCompleteSubarrays(vector<int>& nums) {
4 // Count the total number of distinct elements in the array
5 unordered_set<int> uniqueElements(nums.begin(), nums.end());
6 int totalDistinctCount = uniqueElements.size();
7
8 // Initialize result counter and get array size
9 int result = 0;
10 int arraySize = nums.size();
11
12 // Iterate through all possible starting positions
13 for (int startIndex = 0; startIndex < arraySize; ++startIndex) {
14 // Clear the set to track distinct elements in current subarray
15 uniqueElements.clear();
16
17 // Extend the subarray from startIndex to the end
18 for (int endIndex = startIndex; endIndex < arraySize; ++endIndex) {
19 // Add current element to the set
20 uniqueElements.insert(nums[endIndex]);
21
22 // Check if current subarray contains all distinct elements
23 if (uniqueElements.size() == totalDistinctCount) {
24 // Increment count for each complete subarray found
25 ++result;
26 }
27 }
28 }
29
30 return result;
31 }
32};
33
1/**
2 * Counts the number of complete subarrays in the given array.
3 * A complete subarray is one that contains all distinct elements present in the entire array.
4 *
5 * @param nums - The input array of numbers
6 * @returns The count of complete subarrays
7 */
8function countCompleteSubarrays(nums: number[]): number {
9 // Create a set to find all distinct elements in the entire array
10 const distinctElements: Set<number> = new Set(nums);
11 const totalDistinctCount: number = distinctElements.size;
12 const arrayLength: number = nums.length;
13 let completeSubarrayCount: number = 0;
14
15 // Iterate through each possible starting position
16 for (let startIndex = 0; startIndex < arrayLength; ++startIndex) {
17 // Clear the set to track distinct elements in current subarray
18 distinctElements.clear();
19
20 // Extend the subarray from startIndex to each possible ending position
21 for (let endIndex = startIndex; endIndex < arrayLength; ++endIndex) {
22 // Add current element to the set of distinct elements in subarray
23 distinctElements.add(nums[endIndex]);
24
25 // Check if current subarray contains all distinct elements
26 if (distinctElements.size === totalDistinctCount) {
27 // Increment count if subarray is complete
28 ++completeSubarrayCount;
29 }
30 }
31 }
32
33 return completeSubarrayCount;
34}
35
Time and Space Complexity
Time Complexity: O(n²)
where n
is the length of the input array nums
.
The algorithm uses two nested loops:
- The outer loop runs
n
times (iterating through each starting positioni
) - The inner loop iterates through the subarray from position
i
to the end of the array, which in the worst case isn - i
iterations - The total number of iterations is
n + (n-1) + (n-2) + ... + 1 = n(n+1)/2
, which simplifies toO(n²)
- Within the inner loop, the
set.add()
operation takesO(1)
average time - The comparison
len(s) == cnt
takesO(1)
time
Therefore, the overall time complexity is O(n²)
.
Space Complexity: O(k)
where k
is the number of distinct elements in nums
.
The space usage comes from:
- The initial set creation
set(nums)
which stores all unique elements, requiringO(k)
space - The set
s
created in each iteration of the outer loop, which can contain at mostk
distinct elements - The variable
cnt
stores a single integer:O(1)
- The variables
ans
andn
each store single integers:O(1)
Since k ≤ n
(the number of distinct elements cannot exceed the total number of elements), and the dominant space usage is O(k)
, the overall space complexity is O(k)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding "Complete" Definition
Issue: A common mistake is thinking that once a subarray becomes complete (contains all distinct elements), we should stop extending it. This leads to undercounting.
Example: For nums = [1, 3, 1, 2, 2]
with distinct elements {1, 2, 3}
:
- Starting at index 0:
[1, 3, 1, 2]
is complete - But
[1, 3, 1, 2, 2]
is also complete!
Wrong approach:
for i in range(n):
s = set()
for x in nums[i:]:
s.add(x)
if len(s) == cnt:
ans += 1
break # WRONG: Breaking here misses other complete subarrays
Correct approach: Continue checking even after finding the first complete subarray from each starting position, as all extensions of a complete subarray are also complete.
Pitfall 2: Using Dictionary Instead of Set Inefficiently
Issue: Some might use a dictionary to count element frequencies unnecessarily when only distinct elements matter.
Inefficient approach:
for i in range(n):
freq = {}
for x in nums[i:]:
freq[x] = freq.get(x, 0) + 1
if len(freq) == cnt: # This works but is overkill
ans += 1
Better approach: Use a set since we only care about distinct elements, not their frequencies. Sets have better space efficiency and clearer intent.
Pitfall 3: Optimization Opportunity - Early Termination
Issue: The current solution continues checking subarrays even when it's impossible to form a complete subarray.
Enhancement:
for i in range(n):
# If remaining elements can't possibly contain all distinct elements
if n - i < cnt:
break
s = set()
for j, x in enumerate(nums[i:], start=i):
s.add(x)
if len(s) == cnt:
# Once complete, all further extensions are also complete
ans += (n - j)
break
This optimization recognizes that once we find the first complete subarray starting at position i
, all its extensions are automatically complete, so we can add (n - j)
to the answer and move to the next starting position.
How does merge sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!