2524. Maximum Frequency Score of a Subarray
Problem Description
In this problem, you are provided with an integer array nums
and a positive integer k
. Your task is to find the maximum frequency score of any subarray of size k
.
A frequency score is calculated by taking unique values in the array, raising each to the power of how frequently they appear in the subarray (their frequency), then summing these values up. The final score is computed using the modulo operator with 10^9 + 7
.
Consider an array [5,4,5,7,4,4]. The frequency score is calculated by:
- The number
4
appears 3 times, so4^3 = 64
. - The number
5
appears 2 times, so5^2 = 25
. - The number
7
appears 1 time, so7^1 = 7
.
The frequency score is (64 + 25 + 7) % (10^9 + 7) = 96
.
What you need to find is the subarray of exactly k
elements which has the highest frequency score. A subarray is simply a contiguous segment of the array.
Intuition
To arrive at the solution for this problem, the intuition follows the idea of a sliding window combined with efficient calculation of powers and modulo operations.
Since you need to find the maximum frequency score of any subarray with size k
, you'll want to look at each possible subarray of that size. This is a classic case for using a sliding window technique, that allows you to iterate over subarrays of size k
efficiently without re-calculating everything from scratch for each subarray.
The code must maintain and update a count of the occurrences of each distinct element in the current window. The Counter
from Python's collections library is handy for this task. When the window slides, you update the count for the element that leaves the window (decreasing its frequency) and for the one that enters (increasing its frequency).
Another key part of the intuition behind the solution is handling the modulo operation efficiently. To avoid large intermediate values, the modulo operator should be used smartly during the calculations. The solution involves incrementally updating the total score of the window and applying modulo 10^9 + 7
to keep numbers within a manageable range.
There's a neat little optimization to avoid re-computing powers from scratch. The code updates the contribution to the score of a number using (b - 1) * pow(b, cnt[b], mod)
for a number entering the window and (a - 1) * pow(a, cnt[a] - 1, mod)
for a number exiting the window. This takes advantage of the existing counts to update the score, which limits the need for recalculating exponents.
The mod
variable is used to apply the modulo operator to all intermediate sums so that the final result is under the modulo of 10^9 + 7
.
The solution keeps track of the current score (cur
) and continuously updates it while sliding the window through the array. The maximum score encountered (ans
) is the value returned at the end.
Learn more about Math and Sliding Window patterns.
Solution Approach
The solution makes use of a few important programming and algorithmic concepts: sliding window technique, hashing (via the Counter
from Python's collections library), and modular arithmetic. Here's a step-by-step explanation of the approach:
-
Initialize a variable
mod
to10**9 + 7
to store the modulus value for later operations. -
Use the
Counter
to count the frequency of each distinct element within the first subarray of sizek
innums
. This establishes the initial state of the window. -
Calculate the initial frequency score using a sum of powers of each unique element multiplied by its respective frequency, with each intermediate result taken modulo
mod
. -
Store both the current frequency score (
cur
) and the maximum score (ans
) observed so far. Initially,cur
is the frequency score of the first window andans
is set to the same value. -
Iterate through the elements of
nums
starting from indexk
to the end, effectively sliding the window across the array. For each iteration:- Compute the new frequency score when a new element enters the subarray from the right, and an old element leaves from the left.
- If the incoming and outgoing elements are different, update the current frequency score (
cur
) as follows:- For the element entering the window (let's call it
b
), increment its count and updatecur
by adding(b - 1) * pow(b, cnt[b], mod)
. Ifb
wasn't previously in the counter (i.e.,cnt[b]
is 0), just addb
. - For the element leaving the window (let's call it
a
), decrement its count and updatecur
by subtracting(a - 1) * pow(a, cnt[a] - 1, mod)
. Ifa
's new frequency is now below or equal to 1, just subtracta
.
- For the element entering the window (let's call it
- Apply the modulo operation to the current frequency score to ensure the result stays within the allowed range.
- Update the maximum score
ans
if the newcur
is larger.
-
After finishing the iteration over the array, return the highest frequency score (
ans
) found during the sliding window process.
This approach ensures that only relevant elements are considered for the score calculation and that the calculation is as efficient as possible. The solution effectively handles each subarray of size k
without recalculating the entire frequency score from scratch and uses modular arithmetic to manage large numbers.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example using the solution approach described above.
Given the integer array nums
= [3, 2, 2, 3, 3] and k
= 3, we want to find the maximum frequency score of any subarray of size k
.
-
We initialize our modulus
mod
to10**9 + 7
for the modulo operations. -
Starting with the first subarray [3, 2, 2], we use a
Counter
to count the frequency of each number. We get:- 3: 1 occurrence
- 2: 2 occurrences
-
We calculate the initial frequency score:
- For
3
, the score component is3^1 = 3
. - For
2
, the score component is2^2 = 4
. - Summing them up, we get an initial score
cur
of3 + 4 = 7
.
- For
-
We set
ans
=cur
, soans
= 7 as the maximum score observed so far. -
We slide the window to the next subarray [2, 2, 3] by removing the first element (3) and adding the next element (3) after the initial subarray.
- Before doing that, our current frequency score is
cur = 7
. - The element leaving the window is
3
(frequency was 1). - The element entering the window is another
3
(frequency will be 2). - For the exiting
3
, we subtract3^1 = 3
fromcur
which becomes7 - 3 = 4
. - For the incoming
3
, we add3^2 = 9
since the count of 3 is increasing from 1 to 2. - Our new
cur
is4 + 9 = 13
, so we apply modulo tocur
:cur = 13 % (10^9 + 7) = 13
. - We compare
cur
withans
andcur
is greater, so we updateans
to13
.
- Before doing that, our current frequency score is
-
Next, we continue sliding the window to get to the subarray [2, 3, 3] and perform similar calculations. The element leaving is
2
(its frequency will be 1), and the incoming element is3
(its frequency will be 3).- Our current frequency score
cur
is13
. - For the exiting
2
, we subtract2^1 = 2
fromcur
, which becomes13 - 2 = 11
. - For the incoming
3
, we add3^3 = 27
and our newcur
is11 + 27 = 38
. - After applying the modulo operation,
cur = 38 % (10^9 + 7) = 38
. - We compare
cur
withans
, andcur
is greater, so we updateans
to38
.
- Our current frequency score
-
Having finished sliding the window through the array, we find that the maximum frequency score (
ans
) is38
. This is the final answer.
In conclusion, sliding across the subarray [2, 3, 3], we reach the highest frequency score of 38
for the array nums
= [3, 2, 2, 3, 3] with k
= 3.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def max_frequency_score(self, nums, k):
5 # Define the modulus for the calculations as per the problem statement, which is 10^9 + 7
6 mod = 10**9 + 7
7
8 # Create a Counter for the first 'k' elements in the list
9 counter = Counter(nums[:k])
10
11 # Calculate the initial score by raising 'k' to the power of the frequency count, modulo 'mod'
12 current_score = sum(pow(num, frequency, mod) for num, frequency in counter.items()) % mod
13
14 # Set the 'answer' to the initial score, it will keep track of the highest score
15 answer = current_score
16
17 # Start iterating from the 'k'th element in 'nums'
18 i = k
19 while i < len(nums):
20 # 'previous_num' is the number which will be excluded from the current window
21 # 'new_num' is the number which will be included in the current window
22 previous_num, new_num = nums[i - k], nums[i]
23
24 # Only process if we're examining a new number; otherwise, skip re-calculation
25 if previous_num != new_num:
26 # Increase the score by the power of the count of the new number, if it already exists
27 current_score += (new_num - 1) * pow(new_num, counter[new_num], mod) if counter[new_num] else new_num
28
29 # Decrease the score by the power of the count of the previous number, if necessary
30 current_score -= (previous_num - 1) * pow(previous_num, counter[previous_num] - 1, mod) if counter[previous_num] > 1 else previous_num
31
32 # Ensure the current score does not exceed 'mod'
33 current_score %= mod
34
35 # Update the counter for the new window
36 counter[new_num] += 1
37 counter[previous_num] -= 1
38
39 # Update the answer if the current score is higher
40 answer = max(answer, current_score)
41
42 # Move to the next element
43 i += 1
44
45 # Return the highest score found
46 return answer
47
1import java.util.Map;
2import java.util.HashMap;
3
4class Solution {
5 private final int MOD = (int) 1e9 + 7;
6
7 // Method to find the maximum frequency score of a sequence
8 public int maxFrequencyScore(int[] nums, int k) {
9 // Map to store the frequency of each number
10 Map<Integer, Integer> frequencyMap = new HashMap<>();
11
12 // Populate the map with the frequency of first k numbers
13 for (int i = 0; i < k; ++i) {
14 frequencyMap.merge(nums[i], 1, Integer::sum);
15 }
16
17 long currentScore = 0;
18 // Calculate the initial score based on the first k elements
19 for (var entry : frequencyMap.entrySet()) {
20 currentScore = (currentScore + quickPower(entry.getKey(), entry.getValue())) % MOD;
21 }
22
23 long maxScore = currentScore;
24
25 // Iterate through the rest of the array, updating the score
26 for (int i = k; i < nums.length; ++i) {
27 int removedNum = nums[i - k];
28 int addedNum = nums[i];
29
30 // Update score only if the incoming and outgoing elements are different
31 if (removedNum != addedNum) {
32 // Decrease score for the removed number
33 if (frequencyMap.getOrDefault(addedNum, 0) > 0) {
34 currentScore += (addedNum - 1) * quickPower(addedNum, frequencyMap.get(addedNum)) % MOD;
35 } else {
36 currentScore += addedNum;
37 }
38
39 // Increase score for the added number
40 if (frequencyMap.getOrDefault(removedNum, 0) > 1) {
41 currentScore -= (removedNum - 1) * quickPower(removedNum, frequencyMap.get(removedNum) - 1) % MOD;
42 } else {
43 currentScore -= removedNum;
44 }
45
46 // Normalize the current score to be within the range of MOD
47 currentScore = (currentScore + MOD) % MOD;
48
49 // Update the frequencies of the added and removed numbers
50 frequencyMap.put(addedNum, frequencyMap.getOrDefault(addedNum, 0) + 1);
51 frequencyMap.put(removedNum, frequencyMap.getOrDefault(removedNum, 0) - 1);
52
53 // Check if the current score is the maximum score so far
54 maxScore = Math.max(maxScore, currentScore);
55 }
56 }
57
58 // Cast the max score to int before returning as per the method signature
59 return (int) maxScore;
60 }
61
62 // Helper method to perform quick power (exponentiation by squaring)
63 private long quickPower(long base, long exponent) {
64 long result = 1;
65 while (exponent > 0) {
66 if ((exponent & 1) == 1) {
67 result = result * base % MOD;
68 }
69 base = base * base % MOD;
70 exponent >>= 1;
71 }
72 return result;
73 }
74}
75
1#include <vector>
2#include <unordered_map>
3#include <algorithm>
4
5class Solution {
6public:
7 // Method to calculate the max frequency score
8 int maxFrequencyScore(vector<int>& nums, int k) {
9 using ll = long long; // Define type alias for long long
10 const int mod = 1e9 + 7; // Define the modulus for calculations
11
12 // Quick power function for calculating a^n % mod efficiently
13 auto quickPower = [&](ll base, ll exponent) {
14 ll result = 1;
15 while (exponent > 0) {
16 if (exponent & 1) { // If the current bit is 1
17 result = result * base % mod; // Multiply the base
18 }
19 base = base * base % mod; // Square the base for next bit
20 exponent >>= 1; // Right-shift exponent for next bit
21 }
22 return result;
23 };
24
25 // Count the frequency of each number in the first k elements
26 unordered_map<int, int> frequencyCounter;
27 for (int i = 0; i < k; ++i) {
28 frequencyCounter[nums[i]]++;
29 }
30
31 // Calculate initial score based on frequency
32 ll currentScore = 0;
33 for (auto& [number, count] : frequencyCounter) {
34 currentScore = (currentScore + quickPower(number, count)) % mod;
35 }
36
37 // Initial answer is the current score
38 ll maxScore = currentScore;
39
40 // Slide the window and update the score
41 for (int i = k; i < nums.size(); ++i) {
42 // Old and new numbers at the ends of the current window
43 int oldNumber = nums[i - k], newNumber = nums[i];
44
45 if (oldNumber != newNumber) {
46 // Update current score based on the new and old number counts
47 currentScore += frequencyCounter[newNumber] ? (newNumber - 1) * quickPower(newNumber, frequencyCounter[newNumber]) % mod : newNumber;
48 currentScore -= frequencyCounter[oldNumber] > 1 ? (oldNumber - 1) * quickPower(oldNumber, frequencyCounter[oldNumber] - 1) % mod : oldNumber;
49 // Modulo operation to keep the score within valid range
50 currentScore = (currentScore + mod) % mod;
51 maxScore = std::max(maxScore, currentScore);
52
53 // Update counts for the new and old numbers
54 frequencyCounter[newNumber]++;
55 frequencyCounter[oldNumber]--;
56 }
57 }
58
59 // Return the maximum score obtained
60 return maxScore;
61 }
62};
63
1function maxFrequencyScore(nums: number[], k: number): number {
2 type ll = number; // Using 'number' type to represent long long in this context
3
4 const mod: ll = 1e9 + 7; // Define the modulus for calculations
5
6 // Quick power function for calculating a^n % mod efficiently
7 const quickPower = (base: ll, exponent: ll): ll => {
8 let result: ll = 1;
9 while (exponent > 0) {
10 if (exponent & 1) { // If the current bit is 1
11 result = (result * base) % mod; // Multiply the base
12 }
13 base = (base * base) % mod; // Square the base for next bit
14 exponent >>= 1; // Right-shift exponent for next bit
15 }
16 return result;
17 };
18
19 // Count the frequency of each number in the first k elements
20 const frequencyCounter: Record<number, number> = {};
21 for (let i = 0; i < k; ++i) {
22 frequencyCounter[nums[i]] = (frequencyCounter[nums[i]] || 0) + 1;
23 }
24
25 // Calculate initial score based on frequency
26 let currentScore: ll = 0;
27 for (const number in frequencyCounter) {
28 currentScore = (currentScore + quickPower(+number, frequencyCounter[number])) % mod;
29 }
30
31 // Initial maxScore is the current score
32 let maxScore: ll = currentScore;
33
34 // Slide the window and update the score
35 for (let i = k; i < nums.length; ++i) {
36 // Old and new numbers at the ends of the current window
37 const oldNumber: number = nums[i - k], newNumber: number = nums[i];
38
39 if (oldNumber !== newNumber) {
40 // Update current score based on the new and old number counts
41 currentScore += (frequencyCounter[newNumber] ? ((newNumber - 1) * quickPower(newNumber, frequencyCounter[newNumber])) % mod : newNumber);
42 currentScore -= (frequencyCounter[oldNumber] > 1 ? ((oldNumber - 1) * quickPower(oldNumber, frequencyCounter[oldNumber] - 1)) % mod : oldNumber);
43 // Modulo operation to keep the score within a valid range
44 currentScore = (currentScore % mod + mod) % mod;
45 maxScore = Math.max(maxScore, currentScore);
46
47 // Update counts for the new and old numbers
48 frequencyCounter[newNumber] = (frequencyCounter[newNumber] || 0) + 1;
49 if (frequencyCounter[oldNumber] > 1) {
50 frequencyCounter[oldNumber]--;
51 } else {
52 delete frequencyCounter[oldNumber];
53 }
54 }
55 }
56
57 // Return the maximum score obtained
58 return maxScore;
59}
60
Time and Space Complexity
Time Complexity
The time complexity of the code can be analyzed as follows:
- Sorting the
nums[:k]
list (implicitly done byCounter
initializer): O(k * log(k)) - Iterating over the items in the
Counter
object to calculate the initialans
: O(u), where u is the number of unique elements innums[:k]
. - The main loop runs from
i = k
tolen(nums)
, which gives us O(n - k) iterations, where n is the total number of elements innums
. Inside this loop:- Updating
cur
involves a constant number of arithmetic operations and power calculations: the power calculations can be considered constant time since the exponents are bounded byk
(which is the maximum frequency a number can have innums[:k]
), and modulo is O(1). - The
cnt
dictionary update takes O(1) per iteration because dictionaries in Python offer constant time complexity for setting and getting elements. Therefore, this part of the loop contributes O(n - k) to the time complexity.
- Updating
Combining all parts, the total time complexity is dominated by the largest term, resulting in O(n) for the overall complexity, assuming that k << n
and disregarding the less significant terms.
Space Complexity
The space complexity can be analyzed as follows:
- The
cnt
dictionary space usage is O(u), where u is the number of unique elements innums[:k]
. - Other variables such as
ans
,cur
,i
,a
, andb
use constant space O(1).
Therefore, the overall space complexity of the program is O(u), which is the space needed to store the count of unique elements in the initial segment of nums
.
Learn more about how to find time and space complexity quickly using problem constraints.
How does quick sort divide the problem into subproblems?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
https algomonster s3 us east 2 amazonaws com 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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!