2958. Length of Longest Subarray With at Most K Frequency
Problem Description
You are given an array of integers, nums
, and another integer, k
. The term "frequency" refers to how often an element occurs in the array. A "good" array is defined as an array where each element's frequency is no more than k
. The main objective is to find the length of the longest subarray within nums
that qualifies as "good". Remember, a subarray is a consecutive sequence that forms part of the larger array.
Intuition
To find the longest "good" subarray, we can use a sliding window approach. The "window" in this context is a range of consecutive items in nums
, which we will continuously adjust to find the longest range that meets our frequency criteria.
Here's how we think through it:
- First, understand that a subarray is "good" when none of its elements occur more than
k
times. - We will have two pointers, representing the start and end of the subarray, moving through
nums
. These pointers define our current window. - We grow the window by moving the end pointer forward and track the frequency of each element in the window.
- If the frequency of the new element pushes it over
k
, the subarray isn't "good" anymore. We then need to shrink the window from the start until we remove enough instances of any over-represented element to make the subarray "good" again. - As we shift our window, we'll remember the size of the largest "good" subarray we've seen so far. This is our answer.
The key insight is realizing that we can dynamically adjust our window to find the optimal size. By using a hash map to count element frequencies and a while loop to maintain the "good" criteria, we ensure our solution is efficient and doesn't re-scan parts of the array unnecessarily.
Learn more about Sliding Window patterns.
Solution Approach
The implementation of the solution uses the two-pointer technique combined with a hash map to achieve an efficient way of finding the longest "good" subarray. Here's a detailed walkthrough aligned with the code provided:
-
We start by initializing a
defaultdict(int)
namedcnt
to keep track of the frequency of each element in our current window. -
We then set
ans
to 0, which will eventually hold the length of the longest "good" subarray found. Also, we initialize a pointerj
to 0, which represents the start of the current subarray (the sliding window's left edge). -
The
for
loop begins by iterating over each elementx
in the listnums
, withi
denoting the current position (forming the sliding window's right edge). -
For each element
x
, the codecnt[x] += 1
increments its frequency in the hash map. -
Now we check if the subarray
[j, i]
is not "good" anymore by seeing if the frequency ofx
exceedsk
. Thewhile
loopwhile cnt[x] > k:
is activated if the condition is true. -
Inside the loop, we move
j
to the right (j += 1
) to shrink the window, decrementing the frequency ofnums[j]
incnt
to reflect this change. This process repeats until the frequency ofx
isk
or less, making the current window "good" again. -
After each iteration and making sure the window is "good", the length of the current subarray (
i - j + 1
) is compared with the maximum length found so far (ans
), andans
is updated if the current length is greater. This is done usingans = max(ans, i - j + 1)
. -
Once we've checked all elements, the
for
loop ends, and we returnans
, which now holds the longest length of a "good" subarray.
This algorithm efficiently compromises between expanding and shrinking the window to account for every possible "good" subarray, using the cnt
hash map to keep track of frequencies and two pointers to keep track of the window's bounds. By updating ans
each time we find a longer "good" subarray, we ensure the final answer holds the maximum length required.
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 consider a small example to illustrate the solution approach:
Suppose we are given an array of integers nums = [1, 2, 2, 3, 1, 2, 3]
and another integer k = 2
. We are tasked with finding the length of the longest subarray where no element appears more than k
times.
Following our solution approach:
-
We start with initializing our hash map
cnt
to keep track of the frequencies, a variableans
set to0
to track the length of the longest "good" subarray, and a pointerj
initialized to0
for the start position of our window. -
Next, we begin iterating over
nums
from left to right:-
At
i = 0
,nums[i] = 1
. We incrementcnt[1]
to1
and since all frequencies are withink
, we updateans
to1
. -
At
i = 1
,nums[i] = 2
. We incrementcnt[2]
to1
. The subarray[1, 2]
is "good" andans
is updated to2
. -
At
i = 2
,nums[i] = 2
. We incrementcnt[2]
to2
. The subarray[1, 2, 2]
is still "good", soans
becomes3
. -
At
i = 3
,nums[i] = 3
.cnt[3]
becomes1
, and the subarray[1, 2, 2, 3]
is "good". Updateans
to4
. -
At
i = 4
,nums[i] = 1
. The frequency of1
is now2
, and the subarray[1, 2, 2, 3, 1]
is "good", soans
is now5
. -
At
i = 5
,nums[i] = 2
. Nowcnt[2]
becomes3
, which is overk
. To restore the "good" status, we shiftj
rightward. First, we decrementcnt[1]
by1
as we move past the first element, butcnt[2]
is still3
, so we shiftj
again, decrementingcnt[2]
by1
. Nowcnt[2]
is2
, the subarray[2, 2, 3, 1, 2]
is "good", andans
remains at5
. -
At
i = 6
,nums[i] = 3
. Again, the subarray[2, 2, 3, 1, 2, 3]
is "good" andans
is updated to6
, which is the length of this "good" subarray.
-
-
Since we have gone through all elements, our final answer
ans
is6
, the length of the longest "good" subarray[2, 2, 3, 1, 2, 3]
.
This example clearly demonstrates how we use the two-pointer technique in tandem with the frequency hash map to efficiently determine the longest "good" subarray in nums
.
Solution Implementation
1from collections import defaultdict
2
3class Solution:
4 def max_subarray_length(self, nums: List[int], k: int) -> int:
5 # Initialize a counter to keep track of the frequency of each number
6 frequency_counter = defaultdict(int)
7
8 # `max_length` stores the maximum subarray length found that meets the condition
9 max_length = 0
10
11 # `left_pointer` is used to shrink the subarray from the left
12 left_pointer = 0
13
14 # Iterate over the list of numbers using their index and value
15 for right_pointer, value in enumerate(nums):
16 # Increment the count of the current value
17 frequency_counter[value] += 1
18
19 # If the count of the current value exceeds k, shrink the window from the left
20 while frequency_counter[value] > k:
21 # Decrease the count of the number at the left_pointer position
22 frequency_counter[nums[left_pointer]] -= 1
23
24 # Move the left_pointer to the right, making the subarray smaller
25 left_pointer += 1
26
27 # Calculate the current subarray length and update `max_length` if it's larger
28 current_length = right_pointer - left_pointer + 1
29 max_length = max(max_length, current_length)
30
31 # Return the maximum subarray length found
32 return max_length
33
1class Solution {
2 public int maxSubarrayLength(int[] nums, int k) {
3 // Map to store the frequency of each number in the current subarray
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5 int maxLength = 0; // To store the length of the longest subarray
6
7 // Use the two-pointer technique
8 for (int start = 0, end = 0; end < nums.length; ++end) {
9 // Increase the frequency of the current number
10 frequencyMap.merge(nums[end], 1, Integer::sum);
11
12 // If the frequency of the current number exceeds k, shrink the window from the left
13 while (frequencyMap.get(nums[end]) > k) {
14 frequencyMap.merge(nums[start], -1, Integer::sum);
15 start++; // Move the start index to the right
16 }
17
18 // Calculate the length of the current subarray and update maxLength if it is bigger
19 maxLength = Math.max(maxLength, end - start + 1);
20 }
21
22 // Return the maximum length found
23 return maxLength;
24 }
25}
26
1#include <vector>
2#include <unordered_map>
3#include <algorithm>
4
5class Solution {
6public:
7 // Function to find the length of the longest sub-array with elements appearing no more than 'k' times
8 int maxSubarrayLength(vector<int>& nums, int k) {
9 // Dictionary to keep track of the count of each number in the current window
10 unordered_map<int, int> countMap;
11 // Variable to store the maximum length found
12 int maxLength = 0;
13 // Two pointers defining the current window's boundaries
14 for (int left = 0, right = 0; right < nums.size(); ++right) {
15 // Increment the count of the rightmost element in the current window
16 ++countMap[nums[right]];
17 // If the count of the current element exceeds k, shrink the window from the left
18 while (countMap[nums[right]] > k) {
19 --countMap[nums[left]];
20 ++left; // Move the left pointer to the right
21 }
22 // Update the maximum length if the current window is larger
23 maxLength = max(maxLength, right - left + 1);
24 }
25 // Return the maximum length of the sub-array
26 return maxLength;
27 }
28};
29
1// This function finds the maximum length of a subarray
2// where the majority element (the element that appears more than "k" times) is at most "k"
3function maxSubarrayLength(nums: number[], k: number): number {
4 // Map to store frequency of each number in the current window
5 const frequencyMap: Map<number, number> = new Map();
6 // Variable to store the maximum length found
7 let maxLength = 0;
8
9 // Pointers for the sliding window
10 let start = 0; // Start index of the sliding window
11 let end = 0; // End index of the sliding window
12
13 // Iterate through the array using "end" as the end index of the sliding window
14 for (end = 0; end < nums.length; ++end) {
15 // Update the frequency of the end element of the window
16 frequencyMap.set(nums[end], (frequencyMap.get(nums[end]) ?? 0) + 1);
17
18 // If the current number has occurred more than "k" times, move the start index forward
19 while (frequencyMap.get(nums[end])! > k) {
20 // Decrease the frequency of the start element of the window
21 frequencyMap.set(nums[start], frequencyMap.get(nums[start])! - 1);
22 // Move the start index forward
23 ++start;
24 }
25
26 // Calculate the length of the current window and update maxLength if this is larger
27 maxLength = Math.max(maxLength, end - start + 1);
28 }
29
30 // Return the maximum length found
31 return maxLength;
32}
33
Time and Space Complexity
Time Complexity
The time complexity of the code is O(n)
. This is because the function has a single loop iterating through the elements of nums
from start to end, performing a fixed set of operations for each element. Therefore, the time taken by the function scales linearly with the size of the input list nums
.
Space Complexity
The space complexity of the code is O(n)
. This arises due to the use of the cnt
dictionary that stores the count of elements. In the worst case, where all elements in nums
are unique, the dictionary would contain an entry for each distinct element, leading to a space requirement that scales linearly with the size of the input list nums
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
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
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!