2841. Maximum Sum of Almost Unique Subarray
Problem Description
You have an integer array nums
and two positive integers m
and k
.
Your task is to find the maximum sum among all subarrays of length k
that are almost unique. If no such subarray exists, return 0
.
A subarray is considered almost unique if it contains at least m
distinct elements.
A subarray is a contiguous sequence of elements from the array.
For example:
- If
nums = [1, 2, 1, 3, 4]
,m = 3
, andk = 4
, you need to check all subarrays of length 4 - The subarray
[2, 1, 3, 4]
has 4 distinct elements (≥ 3), so it's almost unique with sum = 10 - The subarray
[1, 2, 1, 3]
has 3 distinct elements (≥ 3), so it's almost unique with sum = 7 - Among all almost unique subarrays of length 4, return the maximum sum
The solution uses a sliding window approach with a hash table to track distinct elements. It maintains a window of size k
, counts the frequency of each element using Counter
, and tracks the current sum. When the number of distinct elements is at least m
, it updates the maximum sum. As the window slides, it adds the new element and removes the old one, updating both the counter and sum accordingly.
Intuition
When we need to find subarrays of a fixed length k
, a sliding window is a natural choice since we're examining consecutive elements. The key insight is that as we slide the window one position to the right, we only need to remove one element from the left and add one element from the right - we don't need to recalculate everything from scratch.
For tracking distinct elements, we need to know not just which elements are present, but also their frequencies. Why? Because when an element leaves the window, we need to know if it was the last occurrence of that element (making it no longer distinct) or if there are still other occurrences remaining in the window.
This leads us to use a hash table (Counter) to track frequencies. When we add an element, we increment its count. When we remove an element, we decrement its count. If the count reaches zero, we remove it from the hash table entirely since it's no longer in the window.
The number of distinct elements at any point is simply the size of our hash table (number of keys with non-zero counts). We can check this against m
to determine if the current window is "almost unique."
For the sum, instead of recalculating the sum of all k
elements each time, we can maintain a running sum. When the window slides, we simply add the new element and subtract the old element: s = s + nums[i] - nums[i-k]
. This optimization reduces the time complexity from O(n*k) to O(n).
By combining these techniques - sliding window for fixed-size subarrays, hash table for tracking distinct elements, and running sum for efficiency - we can solve the problem in a single pass through the array.
Learn more about Sliding Window patterns.
Solution Approach
The solution implements a sliding window technique combined with a hash table to efficiently find the maximum sum of almost unique subarrays.
Initial Window Setup:
First, we initialize the window with the first k
elements:
- Create a
Counter
objectcnt
to track the frequency of elements in the firstk
elements:cnt = Counter(nums[:k])
- Calculate the sum of the first
k
elements:s = sum(nums[:k])
- Check if this initial window is almost unique (has at least
m
distinct elements). If yes, setans = s
, otherwiseans = 0
Sliding the Window:
We then slide the window through the rest of the array from position k
to len(nums) - 1
:
For each position i
:
- Add the new element to the window:
- Increment the count of the new element:
cnt[nums[i]] += 1
- Increment the count of the new element:
- Remove the old element from the window:
- Decrement the count of the element leaving the window:
cnt[nums[i - k]] -= 1
- If the count becomes 0, remove it from the counter entirely:
if cnt[nums[i - k]] == 0: cnt.pop(nums[i - k])
- Decrement the count of the element leaving the window:
- Update the sum efficiently:
- Instead of recalculating the entire sum, adjust it:
s += nums[i] - nums[i - k]
- Instead of recalculating the entire sum, adjust it:
- Check if current window is almost unique:
- If
len(cnt) >= m
(at leastm
distinct elements), update the maximum:ans = max(ans, s)
- If
Why This Works:
- The
Counter
maintains exact frequencies, solen(cnt)
gives us the number of distinct elements - Removing elements with zero count ensures
len(cnt)
accurately reflects distinct elements in the current window - The sliding window ensures we check all possible subarrays of length
k
- The running sum optimization reduces time complexity from O(n*k) to O(n)
Time Complexity: O(n) where n is the length of the array Space Complexity: O(k) for storing at most k distinct elements in the hash table
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, 1, 3, 4]
, m = 3
, and k = 4
.
Step 1: Initialize the first window [1, 2, 1, 3]
- Create counter:
cnt = {1: 2, 2: 1, 3: 1}
(element 1 appears twice) - Calculate sum:
s = 1 + 2 + 1 + 3 = 7
- Check distinct elements:
len(cnt) = 3
(elements are 1, 2, 3) - Since 3 ≥ 3, this window is almost unique, so
ans = 7
Step 2: Slide window to position i = 4, new window [2, 1, 3, 4]
Add new element (nums[4] = 4):
cnt[4] += 1
→cnt = {1: 2, 2: 1, 3: 1, 4: 1}
Remove old element (nums[0] = 1):
cnt[1] -= 1
→cnt[1] = 1
(still one occurrence of 1 remaining)- Since count isn't 0, keep it in counter:
cnt = {1: 1, 2: 1, 3: 1, 4: 1}
Update sum:
s = 7 + 4 - 1 = 10
Check if almost unique:
len(cnt) = 4
distinct elements (1, 2, 3, 4)- Since 4 ≥ 3, window is almost unique
- Update maximum:
ans = max(7, 10) = 10
Result: The maximum sum among all almost unique subarrays of length 4 is 10
.
This example demonstrates how the sliding window efficiently moves through the array, maintaining the counter and sum with minimal operations - we only update for the one element entering and one element leaving, rather than recalculating everything from scratch.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def maxSum(self, nums: List[int], m: int, k: int) -> int:
6 # Initialize a counter for the first window of size k
7 window_counter = Counter(nums[:k])
8
9 # Calculate the sum of the first window
10 window_sum = sum(nums[:k])
11
12 # Initialize answer: if first window has at least m distinct elements, use its sum
13 max_sum = window_sum if len(window_counter) >= m else 0
14
15 # Slide the window through the rest of the array
16 for i in range(k, len(nums)):
17 # Add the new element to the window
18 window_counter[nums[i]] += 1
19
20 # Remove the leftmost element from the window
21 window_counter[nums[i - k]] -= 1
22
23 # Update the window sum
24 window_sum += nums[i] - nums[i - k]
25
26 # If count becomes 0, remove the element from counter entirely
27 if window_counter[nums[i - k]] == 0:
28 window_counter.pop(nums[i - k])
29
30 # If current window has at least m distinct elements, update max sum
31 if len(window_counter) >= m:
32 max_sum = max(max_sum, window_sum)
33
34 return max_sum
35
1class Solution {
2 public long maxSum(List<Integer> nums, int m, int k) {
3 // Map to track frequency of each number in current window
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5 int n = nums.size();
6 long currentSum = 0;
7
8 // Initialize the first window of size k
9 for (int i = 0; i < k; i++) {
10 // Add current number to frequency map
11 frequencyMap.merge(nums.get(i), 1, Integer::sum);
12 // Add to current window sum
13 currentSum += nums.get(i);
14 }
15
16 // Initialize answer: if first window has at least m distinct elements, use its sum
17 long maxSum = frequencyMap.size() >= m ? currentSum : 0;
18
19 // Slide the window through the rest of the array
20 for (int i = k; i < n; i++) {
21 // Add new element entering the window
22 frequencyMap.merge(nums.get(i), 1, Integer::sum);
23
24 // Remove element leaving the window
25 int leftElement = nums.get(i - k);
26 if (frequencyMap.merge(leftElement, -1, Integer::sum) == 0) {
27 // If frequency becomes 0, remove the element from map
28 frequencyMap.remove(leftElement);
29 }
30
31 // Update current window sum
32 currentSum = currentSum + nums.get(i) - nums.get(i - k);
33
34 // Update maximum sum if current window has at least m distinct elements
35 if (frequencyMap.size() >= m) {
36 maxSum = Math.max(maxSum, currentSum);
37 }
38 }
39
40 return maxSum;
41 }
42}
43
1class Solution {
2public:
3 long long maxSum(vector<int>& nums, int m, int k) {
4 // HashMap to track frequency of elements in current window
5 unordered_map<int, int> frequency;
6
7 // Current window sum
8 long long windowSum = 0;
9 int n = nums.size();
10
11 // Initialize first window of size k
12 for (int i = 0; i < k; ++i) {
13 frequency[nums[i]]++;
14 windowSum += nums[i];
15 }
16
17 // Initialize answer: if first window has at least m distinct elements, use its sum
18 long long maxWindowSum = (frequency.size() >= m) ? windowSum : 0;
19
20 // Slide the window through the rest of the array
21 for (int i = k; i < n; ++i) {
22 // Add new element to the window (right side)
23 frequency[nums[i]]++;
24
25 // Remove leftmost element from the window
26 int leftElement = nums[i - k];
27 frequency[leftElement]--;
28
29 // If element count becomes 0, remove it from map to maintain accurate distinct count
30 if (frequency[leftElement] == 0) {
31 frequency.erase(leftElement);
32 }
33
34 // Update window sum: add new element, subtract removed element
35 windowSum = windowSum + nums[i] - nums[i - k];
36
37 // Update maximum sum if current window has at least m distinct elements
38 if (frequency.size() >= m) {
39 maxWindowSum = max(maxWindowSum, windowSum);
40 }
41 }
42
43 return maxWindowSum;
44 }
45};
46
1/**
2 * Finds the maximum sum of a subarray of length k with at least m distinct elements
3 * @param nums - The input array of numbers
4 * @param m - Minimum number of distinct elements required in the subarray
5 * @param k - Length of the subarray
6 * @returns Maximum sum of valid subarray, or 0 if no valid subarray exists
7 */
8function maxSum(nums: number[], m: number, k: number): number {
9 const arrayLength: number = nums.length;
10
11 // Map to track frequency of elements in current window
12 const frequencyMap: Map<number, number> = new Map();
13
14 // Initialize window with first k elements
15 let currentWindowSum: number = 0;
16 for (let i = 0; i < k; i++) {
17 const currentNum: number = nums[i];
18 frequencyMap.set(currentNum, (frequencyMap.get(currentNum) || 0) + 1);
19 currentWindowSum += currentNum;
20 }
21
22 // Check if initial window meets the distinct elements requirement
23 let maxSumResult: number = frequencyMap.size >= m ? currentWindowSum : 0;
24
25 // Slide the window through the rest of the array
26 for (let i = k; i < arrayLength; i++) {
27 const incomingNum: number = nums[i];
28 const outgoingNum: number = nums[i - k];
29
30 // Add new element to the window
31 frequencyMap.set(incomingNum, (frequencyMap.get(incomingNum) || 0) + 1);
32
33 // Remove the element that's sliding out of the window
34 const outgoingFrequency: number = frequencyMap.get(outgoingNum)! - 1;
35 frequencyMap.set(outgoingNum, outgoingFrequency);
36
37 // Remove from map if frequency becomes zero
38 if (outgoingFrequency === 0) {
39 frequencyMap.delete(outgoingNum);
40 }
41
42 // Update window sum
43 currentWindowSum = currentWindowSum + incomingNum - outgoingNum;
44
45 // Update maximum sum if current window has enough distinct elements
46 if (frequencyMap.size >= m) {
47 maxSumResult = Math.max(maxSumResult, currentWindowSum);
48 }
49 }
50
51 return maxSumResult;
52}
53
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the input array nums
.
The algorithm performs a single pass through the array starting from index k
to n-1
. In the initial setup, we process the first k
elements to build the counter and calculate the initial sum, which takes O(k)
time. Then, the main loop iterates n-k
times. Within each iteration, all operations are constant time:
- Adding and removing elements from the counter:
O(1)
- Updating the sum:
O(1)
- Checking and removing zero-count entries:
O(1)
- Checking the counter size and updating the maximum:
O(1)
Since k ≤ n
, the total time complexity is O(k) + O(n-k) = O(n)
.
Space Complexity: O(k)
, where k
is the size of the sliding window.
The counter (cnt
) stores at most k
distinct elements at any given time, since it represents the elements within a sliding window of size k
. Even though elements are added and removed as the window slides, the maximum number of unique elements in the counter cannot exceed k
. The remaining variables (s
, ans
, i
) use constant space O(1)
. Therefore, the overall space complexity is O(k)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Edge Cases Where k > len(nums)
The code assumes that the array has at least k
elements. If k > len(nums)
, the slicing operation nums[:k]
will only return the entire array, and the loop range(k, len(nums))
won't execute at all, potentially returning an incorrect result.
Solution: Add a check at the beginning to handle this edge case:
def maxSum(self, nums: List[int], m: int, k: int) -> int:
if k > len(nums):
return 0
# rest of the code...
2. Forgetting to Remove Elements with Zero Count from Counter
A critical mistake is forgetting to remove elements with zero count from the counter. If you only decrement without removing zero-count elements, len(window_counter)
will incorrectly include elements that are no longer in the window, leading to wrong distinct element counts.
Incorrect approach:
# Only decrementing without removing window_counter[nums[i - k]] -= 1 # len(window_counter) might still count this element even if its count is 0
Correct approach:
window_counter[nums[i - k]] -= 1 if window_counter[nums[i - k]] == 0: window_counter.pop(nums[i - k])
3. Incorrect Order of Operations When Sliding the Window
The order matters when updating the counter and sum. A common mistake is removing the old element before adding the new one, which could lead to issues if the same element is being added and removed.
Problematic order (if same element entering and leaving):
# If nums[i] == nums[i-k], this could cause issues window_counter[nums[i - k]] -= 1 if window_counter[nums[i - k]] == 0: window_counter.pop(nums[i - k]) # Might remove the element completely window_counter[nums[i]] += 1 # Then have to re-add it
Better approach (as shown in the solution):
# Add first, then remove window_counter[nums[i]] += 1 window_counter[nums[i - k]] -= 1 if window_counter[nums[i - k]] == 0: window_counter.pop(nums[i - k])
4. Recalculating the Entire Sum Instead of Using Incremental Updates
While not incorrect, recalculating the sum of the entire window at each step is inefficient and defeats the purpose of the sliding window optimization.
Inefficient approach:
# O(k) operation for each window position
window_sum = sum(nums[i-k+1:i+1])
Efficient approach:
# O(1) operation window_sum += nums[i] - nums[i - k]
5. Not Initializing max_sum Correctly When No Valid Window Exists
If the first window doesn't meet the "almost unique" criteria (has fewer than m
distinct elements), and no subsequent windows do either, the function should return 0, not leave max_sum
uninitialized or with an incorrect value.
Correct initialization ensures proper handling:
max_sum = window_sum if len(window_counter) >= m else 0
Which of the following is a good use case for backtracking?
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!