Maximum Sum of Almost Unique Subarray
MedPrompt
You are given an integer array nums and two positive integers m and k.
Return the maximum sum out of all almost unique subarrays of length k of nums. If no such subarray exists, return 0.
A subarray of nums is almost unique if it contains at least m distinct elements.
A subarray is a contiguous non-empty sequence of elements within an array.
Example 1:
k = 4Example 2:
Input: nums = [5,9,9,2,4,5,4], m = 1, k = 3
Output: 23
Explanation: There are 5 almost unique subarrays of size k. These subarrays are [5, 9, 9], [9, 9, 2], [9, 2, 4], [2, 4, 5], and [4, 5, 4]. Among these subarrays, the one with the maximum sum is [5, 9, 9] which has a sum of 23.Example 3:
k = 3
Constraints:
1 <= nums.length <= 2 * 1041 <= m <= k <= nums.length1 <= nums[i] <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through all possible subarrays of length k. For each subarray, we check if it meets the 'almost unique' criteria (at least m distinct elements) and, if so, calculate its sum. We keep track of the maximum sum found across all valid subarrays.
Algorithm
- Initialize
maxSumto 0. - Iterate through the array
numsfrom indexi = 0ton - k. - For each
i, create a newHashSetand initializecurrentSumto 0. - Create a subarray of length
kstarting ati. - Iterate through this subarray (from
j = itoi + k - 1): a. Addnums[j]to theHashSet. b. Addnums[j]tocurrentSum. - After iterating through the subarray, check if the size of the
HashSetis greater than or equal tom. - If it is, update
maxSum = Math.max(maxSum, currentSum). - After the outer loop finishes, return
maxSum.
Walkthrough
The brute-force method systematically examines every contiguous subarray of the specified length k.
- We use a loop that starts from the first element and ends at the last possible starting position for a subarray of length
k(nums.length - k). - Inside this loop, for each starting position
i, we form a subarraynums[i...i+k-1]. - To determine if this subarray is 'almost unique', we use a
HashSet. We populate the set with the elements of the current subarray. The size of theHashSetgives us the count of distinct elements. - If the count of distinct elements is
mor more, we proceed to calculate the sum of the elements in this subarray. - This sum is then compared with a running maximum, which is updated if the current sum is greater.
- After checking all possible subarrays, the final maximum sum is returned. If no 'almost unique' subarray is found, the initial maximum sum of 0 is returned.
import java.util.HashSet;import java.util.Set; class Solution { public long maximumSubarraySum(int[] nums, int m, int k) { long maxSum = 0; int n = nums.length; if (k > n) { return 0; } for (int i = 0; i <= n - k; i++) { Set<Integer> distinctElements = new HashSet<>(); long currentSum = 0; // Consider subarray nums[i...i+k-1] for (int j = i; j < i + k; j++) { distinctElements.add(nums[j]); currentSum += nums[j]; } // Check if the subarray is almost unique if (distinctElements.size() >= m) { maxSum = Math.max(maxSum, currentSum); } } return maxSum; }}Complexity
Time
O(n * k), where `n` is the length of `nums`. The outer loop runs `n-k+1` times, and for each iteration, we loop `k` times to build the set and calculate the sum. This results in a quadratic time complexity in the worst case (when `k` is proportional to `n`).
Space
O(k), as we use a `HashSet` to store up to `k` elements for each subarray.
Trade-offs
Pros
Simple to conceptualize and implement.
Correct for all cases, though not efficient.
Cons
Highly inefficient due to redundant calculations.
The time complexity of O(n*k) will lead to a 'Time Limit Exceeded' (TLE) error on larger test cases.
A more efficient approach is to use a sliding window of size k. Instead of re-calculating the sum and distinct elements for each subarray, we maintain these values as the window slides across the array. We add the new element entering the window and remove the element leaving it, updating the sum and the count of distinct elements in constant time on average.
Algorithm
- Initialize
maxSum = 0,currentSum = 0, and aHashMapcalledfreqMapto store element frequencies. - Create the initial window of the first
kelements: a. Iterate fromi = 0tok-1. b. Addnums[i]tocurrentSum. c. Update the frequency ofnums[i]infreqMap. - Check if the initial window is 'almost unique' (
freqMap.size() >= m). If so, setmaxSum = currentSum. - Slide the window across the rest of the array, from
i = kton-1: a. Add the new elementnums[i]to the window:currentSum += nums[i]and update its count infreqMap. b. Remove the leftmost elementnums[i-k]from the window:currentSum -= nums[i-k]and decrement its count infreqMap. c. If the count ofnums[i-k]becomes 0 after decrementing, remove it fromfreqMap. d. Check if the current window is 'almost unique' (freqMap.size() >= m). If so, updatemaxSum = Math.max(maxSum, currentSum). - Return
maxSum.
Walkthrough
This optimized method avoids the repeated work of the brute-force approach by using a sliding window.
- We maintain a window of size
kand aHashMapto store the frequency of each number within that window. The size of theHashMapdirectly tells us the number of distinct elements. - We also keep a running sum of the elements in the window.
- First, we initialize the window with the first
kelements of the array. We calculate their sum and populate the frequency map. We check if this initial window is 'almost unique' and update our maximum sum if it is. - Then, we iterate from the
k-th element to the end of the array. In each step, we 'slide' the window one position to the right:- Add new element: The element at the right end of the new window (
nums[i]) is added. We update the sum and its frequency in the map. - Remove old element: The element that just left the window from the left end (
nums[i-k]) is removed. We update the sum and its frequency. If its frequency becomes zero, we remove the element from the map entirely to keep the map's size accurate for the distinct count.
- Add new element: The element at the right end of the new window (
- After each slide, we check if the number of distinct elements (the map's size) is at least
m. If it is, we compare the current window's sum with our overall maximum sum and update it if necessary. - This process continues until the window has passed over the entire array.
import java.util.HashMap;import java.util.Map; class Solution { public long maximumSubarraySum(int[] nums, int m, int k) { long maxSum = 0; long currentSum = 0; int n = nums.length; Map<Integer, Integer> freqMap = new HashMap<>(); // Initialize the first window for (int i = 0; i < k; i++) { currentSum += nums[i]; freqMap.put(nums[i], freqMap.getOrDefault(nums[i], 0) + 1); } // Check the first window if (freqMap.size() >= m) { maxSum = currentSum; } // Slide the window from k to the end for (int i = k; i < n; i++) { // Add the new element to the window currentSum += nums[i]; freqMap.put(nums[i], freqMap.getOrDefault(nums[i], 0) + 1); // Remove the element that is leaving the window int oldElement = nums[i - k]; currentSum -= oldElement; freqMap.put(oldElement, freqMap.get(oldElement) - 1); if (freqMap.get(oldElement) == 0) { freqMap.remove(oldElement); } // Check if the current window is almost unique if (freqMap.size() >= m) { maxSum = Math.max(maxSum, currentSum); } } return maxSum; }}Complexity
Time
O(n), where `n` is the length of `nums`. We iterate through the array once. The HashMap operations (get, put, remove) take O(1) on average.
Space
O(k), as the `HashMap` stores at most `k` distinct elements from the window.
Trade-offs
Pros
Optimal time complexity of O(n).
Efficiently reuses calculations from the previous window.
Passes all test cases within the time limit.
Cons
Slightly more complex to implement due to managing the sliding window state (sum and frequency map).
Solutions
Solution
public class Solution { public long MaxSum(IList < int > nums, int m, int k) { Dictionary < int, int > cnt = new Dictionary < int, int > (); int n = nums.Count; long s = 0; for (int i = 0; i < k; ++i) { if (!cnt.ContainsKey(nums[i])) { cnt[nums[i]] = 1; } else { cnt[nums[i]]++; } s += nums[i]; } long ans = cnt.Count >= m ? s : 0; for (int i = k; i < n; ++i) { if (!cnt.ContainsKey(nums[i])) { cnt[nums[i]] = 1; } else { cnt[nums[i]]++; } if (cnt.ContainsKey(nums[i - k])) { cnt[nums[i - k]]--; if (cnt[nums[i - k]] == 0) { cnt.Remove(nums[i - k]); } } s += nums[i] - nums[i - k]; if (cnt.Count >= m) { ans = Math.Max(ans, s); } } return ans; }}class Solution {public long maxSum(List<Integer> nums, int m, int k) { Map<Integer, Integer> cnt = new HashMap<>(); int n = nums.size(); long s = 0; for (int i = 0; i < k; ++i) { cnt.merge(nums.get(i), 1, Integer : : sum); s += nums.get(i); } long ans = 0; if (cnt.size() >= m) { ans = s; } for (int i = k; i < n; ++i) { cnt.merge(nums.get(i), 1, Integer : : sum); if (cnt.merge(nums.get(i - k), -1, Integer : : sum) == 0) { cnt.remove(nums.get(i - k)); } s += nums.get(i) - nums.get(i - k); if (cnt.size() >= m) { ans = Math.max(ans, s); } } return ans; }}class Solution {public: long long maxSum(vector<int> &nums, int m, int k) { unordered_map<int, int> cnt; long long s = 0; int n = nums.size(); for (int i = 0; i < k; ++i) { cnt[nums[i]]++; s += nums[i]; } long long ans = cnt.size() >= m ? s : 0; for (int i = k; i < n; ++i) { cnt[nums[i]]++; if (--cnt[nums[i - k]] == 0) { cnt.erase(nums[i - k]); } s += nums[i] - nums[i - k]; if (cnt.size() >= m) { ans = max(ans, s); } } return ans; }};class Solution: def maxSum(self, nums: List[int], m: int, k: int) -> int: cnt = Counter(nums[: k]) s = sum(nums[: k]) ans = 0 if len(cnt) >= m: ans = s for i in range(k, len(nums)): cnt[nums[i]] += 1 cnt[nums[i - k]] -= 1 s += nums[i] - nums[i - k] if cnt[nums[i - k]] == 0: cnt . pop(nums[i - k]) if len(cnt) >= m: ans = max(ans, s) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.