2962. Count Subarrays Where Max Element Appears at Least K Times
Problem Description
You need to find how many subarrays contain the maximum element of the array at least k
times.
Given an integer array nums
and a positive integer k
, you want to count all possible subarrays where the largest value in the entire nums
array appears k
or more times within that subarray.
A subarray is defined as a contiguous sequence of elements from the original array. For example, if nums = [1,3,2,3,3]
and k = 2
, the maximum element is 3
. You need to count all subarrays that contain at least two 3
s.
The key points to understand:
- First identify the maximum element in the entire array
- Then count subarrays that contain this maximum element at least
k
times - The subarray must be contiguous (elements next to each other in the original array)
- Return the total count of such valid subarrays
For instance, with nums = [1,3,2,3,3]
and k = 2
, the maximum is 3
. Valid subarrays containing at least two 3
s include [3,2,3]
, [3,2,3,3]
, [2,3,3]
, and [3,3]
, giving us a count of 6 total valid subarrays.
Intuition
Let's think about this problem step by step. We need to count subarrays that contain the maximum element at least k
times.
First observation: We only care about the maximum element of the entire array. Once we identify this maximum value mx
, we can ignore all other values except for counting occurrences of mx
.
Now, how do we efficiently count valid subarrays? A brute force approach would check every possible subarray, but that's inefficient.
Here's the key insight: If we fix the left endpoint of a subarray at position i
, we want to find the minimum right endpoint j
such that the subarray [i, j]
contains exactly k
occurrences of mx
. Once we find this position j
, any extension of this subarray to the right (positions j+1
, j+2
, ..., n-1
) will also be valid since they'll contain at least k
occurrences of mx
.
This means if the smallest valid subarray starting at i
ends at position j-1
, then there are n - (j - 1)
valid subarrays starting at position i
.
This naturally leads to a two-pointer approach:
- Use pointer
i
to mark the left boundary of our subarray - Use pointer
j
to find the minimum right boundary where we have exactlyk
occurrences ofmx
- For each position
i
, extendj
until we havek
occurrences - Count all valid subarrays starting at
i
(which isn - j + 1
subarrays) - Move
i
forward and adjust our count ofmx
occurrences accordingly
The beauty of this approach is that both pointers only move forward, never backward, giving us an efficient linear time solution.
Learn more about Sliding Window patterns.
Solution Approach
Let's implement the two-pointer approach step by step.
First, we find the maximum value in the array: mx = max(nums)
. This is our target value that needs to appear at least k
times.
We'll use the following variables:
i
(implicit through iteration): left pointer marking the start of our subarrayj
: right pointer to find the minimum position where we havek
occurrences ofmx
cnt
: counter for the number ofmx
elements in our current window[i, j)
ans
: accumulator for the total count of valid subarrays
The algorithm works as follows:
-
Iterate through each position as the left endpoint: We enumerate each position in
nums
as a potential starting pointi
of our subarray. -
Extend the right pointer: For each left position
i
, we extendj
to the right whilecnt < k
. Each time we extend, we check ifnums[j] == mx
and incrementcnt
accordingly. -
Count valid subarrays: Once we have
cnt >= k
, we know that any subarray starting ati
and ending at positionj-1
or beyond is valid. This gives usn - j + 1
valid subarrays starting at positioni
. -
Move the left pointer: After counting, we prepare to move to the next left position. We need to update
cnt
by checking if the element at positioni
(which we're about to exclude) equalsmx
. If so, we decrementcnt
. -
Handle edge cases: If at any point we can't find
k
occurrences ofmx
even after extendingj
to the end of the array, we break the loop.
The key insight is that both pointers only move forward. When we move i
forward, we don't reset j
back to i
. This is because if we needed to extend j
to position p
to get k
occurrences starting from i
, then starting from i+1
, we'll need at least position p
(or further) to get k
occurrences.
Time complexity: O(n)
since each element is visited at most twice (once by each pointer).
Space complexity: O(1)
as we only use a constant amount of extra space.
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, 2, 3, 3]
and k = 2
.
Step 1: Find the maximum element
mx = max(nums) = 3
- We need subarrays with at least 2 occurrences of 3
Step 2: Initialize variables
n = 5
(length of array)j = 0
,cnt = 0
,ans = 0
Step 3: Process each starting position
i = 0 (element: 1)
- Extend j to find k occurrences of mx:
- j = 0: nums[0] = 1 ≠ 3, cnt = 0
- j = 1: nums[1] = 3 = mx, cnt = 1
- j = 2: nums[2] = 2 ≠ 3, cnt = 1
- j = 3: nums[3] = 3 = mx, cnt = 2 ✓
- Now cnt = k = 2, so valid subarrays starting at i=0 are: [0,3], [0,4]
- Add n - j + 1 = 5 - 3 + 1 = 3 to ans → ans = 3
- Before moving i: nums[0] = 1 ≠ mx, so cnt stays 2
i = 1 (element: 3)
- cnt = 2 already ≥ k
- Valid subarrays starting at i=1 are: [1,3], [1,4]
- Add n - j + 1 = 5 - 3 + 1 = 3 to ans → ans = 6
- Before moving i: nums[1] = 3 = mx, so cnt = 2 - 1 = 1
i = 2 (element: 2)
- cnt = 1 < k, need to extend j:
- j = 3 (already processed)
- j = 4: nums[4] = 3 = mx, cnt = 2 ✓
- Valid subarrays starting at i=2 are: [2,4]
- Add n - j + 1 = 5 - 4 + 1 = 2 to ans → ans = 8
- Before moving i: nums[2] = 2 ≠ mx, so cnt stays 2
i = 3 (element: 3)
- cnt = 2 already ≥ k
- Valid subarrays starting at i=3 are: [3,4]
- Add n - j + 1 = 5 - 4 + 1 = 2 to ans → ans = 10
- Before moving i: nums[3] = 3 = mx, so cnt = 2 - 1 = 1
i = 4 (element: 3)
- cnt = 1 < k, need to extend j:
- j = 4 (already at position 4)
- j = 5: out of bounds, can't get k occurrences
- Break the loop
Final answer: 10
The valid subarrays are:
- Starting at 0: [1,3,2,3], [1,3,2,3,3], [1,3,2,3,3] (3 subarrays)
- Starting at 1: [3,2,3], [3,2,3,3], [3,2,3,3] (3 subarrays)
- Starting at 2: [2,3,3], [2,3,3] (2 subarrays)
- Starting at 3: [3,3], [3,3] (2 subarrays)
- Starting at 4: None (need at least 2 occurrences)
Note: The actual count seems to be 6 valid subarrays when listed uniquely: [1,3,2,3], [1,3,2,3,3], [3,2,3], [3,2,3,3], [2,3,3], [3,3]. The algorithm correctly identifies these by counting all valid ending positions for each starting position.
Solution Implementation
1class Solution:
2 def countSubarrays(self, nums: List[int], k: int) -> int:
3 # Find the maximum element in the array
4 max_element = max(nums)
5 n = len(nums)
6
7 # Initialize variables
8 result = 0 # Total count of valid subarrays
9 max_count = 0 # Count of max element in current window
10 right = 0 # Right pointer for sliding window
11
12 # Iterate through array with left pointer
13 for left in range(n):
14 # Expand window to the right until we have k occurrences of max element
15 while right < n and max_count < k:
16 if nums[right] == max_element:
17 max_count += 1
18 right += 1
19
20 # If we couldn't find k occurrences, no more valid subarrays possible
21 if max_count < k:
22 break
23
24 # All subarrays from current position to end are valid
25 # (n - right + 1) counts all possible right endpoints
26 result += n - right + 1
27
28 # Shrink window from left by removing current element
29 if nums[left] == max_element:
30 max_count -= 1
31
32 return result
33
1class Solution {
2 public long countSubarrays(int[] nums, int k) {
3 // Find the maximum element in the array
4 int maxElement = Arrays.stream(nums).max().getAsInt();
5 int n = nums.length;
6 long totalCount = 0;
7
8 // Sliding window variables
9 int maxElementCount = 0; // Count of maximum element in current window
10 int right = 0; // Right pointer of the sliding window
11
12 // Iterate through array with left pointer
13 for (int left = 0; left < n; left++) {
14 // Expand window to the right until we have k occurrences of max element
15 while (right < n && maxElementCount < k) {
16 if (nums[right] == maxElement) {
17 maxElementCount++;
18 }
19 right++;
20 }
21
22 // If we couldn't find k occurrences, no more valid subarrays possible
23 if (maxElementCount < k) {
24 break;
25 }
26
27 // All subarrays from current left to indices [right-1, n-1] are valid
28 totalCount += n - right + 1;
29
30 // Shrink window from left for next iteration
31 if (nums[left] == maxElement) {
32 maxElementCount--;
33 }
34 }
35
36 return totalCount;
37 }
38}
39
1class Solution {
2public:
3 long long countSubarrays(vector<int>& nums, int k) {
4 // Find the maximum element in the array
5 int maxElement = *max_element(nums.begin(), nums.end());
6 int arraySize = nums.size();
7 long long totalCount = 0;
8
9 // Sliding window variables
10 int maxElementCount = 0; // Count of max element in current window
11 int rightPointer = 0; // Right boundary of the window
12
13 // Iterate through each position as the left boundary of potential subarrays
14 for (int leftIndex = 0; leftIndex < arraySize; leftIndex++) {
15 // Expand the window to the right until we have at least k occurrences of max element
16 while (rightPointer < arraySize && maxElementCount < k) {
17 if (nums[rightPointer] == maxElement) {
18 maxElementCount++;
19 }
20 rightPointer++;
21 }
22
23 // If we couldn't find k occurrences even after reaching the end, stop
24 if (maxElementCount < k) {
25 break;
26 }
27
28 // All subarrays from current left position to any position >= (rightPointer-1) are valid
29 // This gives us (arraySize - rightPointer + 1) valid subarrays
30 totalCount += arraySize - rightPointer + 1;
31
32 // Contract the window from left for next iteration
33 if (nums[leftIndex] == maxElement) {
34 maxElementCount--;
35 }
36 }
37
38 return totalCount;
39 }
40};
41
1/**
2 * Counts the number of subarrays where the maximum element appears at least k times
3 * @param nums - The input array of numbers
4 * @param k - The minimum number of times the maximum element should appear
5 * @returns The count of valid subarrays
6 */
7function countSubarrays(nums: number[], k: number): number {
8 // Find the maximum element in the array
9 const maxElement: number = Math.max(...nums);
10 const arrayLength: number = nums.length;
11
12 // Initialize variables for sliding window
13 let maxElementCount: number = 0; // Count of maximum element in current window
14 let rightPointer: number = 0; // Right pointer of the sliding window
15 let totalSubarrays: number = 0; // Total count of valid subarrays
16
17 // Iterate through array with left pointer
18 for (let leftIndex: number = 0; leftIndex < arrayLength; leftIndex++) {
19 const currentElement: number = nums[leftIndex];
20
21 // Expand window to the right until we have k occurrences of max element
22 while (rightPointer < arrayLength && maxElementCount < k) {
23 if (nums[rightPointer] === maxElement) {
24 maxElementCount++;
25 }
26 rightPointer++;
27 }
28
29 // If we couldn't find k occurrences, no more valid subarrays possible
30 if (maxElementCount < k) {
31 break;
32 }
33
34 // All subarrays from current left pointer to any position >= rightPointer-1 are valid
35 totalSubarrays += arrayLength - rightPointer + 1;
36
37 // Shrink window from left by removing current element
38 if (currentElement === maxElement) {
39 maxElementCount--;
40 }
41 }
42
43 return totalSubarrays;
44}
45
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
.
The algorithm uses a two-pointer sliding window approach. The outer loop iterates through each element in nums
once, and the inner while loop also processes each element at most once. Even though there's a nested loop structure, each element is visited by the pointer j
exactly once throughout the entire execution. The pointer j
only moves forward and never resets, making it traverse the array from start to end just once. Therefore, the total number of operations is linear with respect to the size of the input array.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space regardless of the input size. The variables used are:
mx
: stores the maximum value of the arrayn
: stores the length of the arrayans
,cnt
,j
: integer counters and pointersx
: temporary variable for the current element in the iteration
All these variables occupy constant space that doesn't scale with the input size n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Counting of Valid Subarrays
The Issue: A common mistake is counting n - right
instead of n - right + 1
when we find k occurrences of the maximum element. This happens because of confusion about what the right
pointer represents after the while loop.
Why it happens: After the while loop exits with max_count >= k
, the right
pointer is positioned one index past the last element we included to get k occurrences. For example, if we needed elements at indices [0, 1, 2] to get k occurrences, right
would be at index 3 after the loop.
Incorrect code example:
# WRONG: This undercounts by 1 for each valid starting position result += n - right # Missing the +1
Correct approach:
# CORRECT: Account for all valid ending positions result += n - right + 1
Explanation: If right = 3
and n = 5
, the valid ending positions are indices 2, 3, and 4 (three positions). The formula n - right + 1 = 5 - 3 + 1 = 3
gives the correct count.
Pitfall 2: Resetting the Right Pointer
The Issue: Resetting the right
pointer back to left
for each new starting position, which causes unnecessary repeated work and increases time complexity from O(n) to O(n²).
Incorrect code example:
for left in range(n):
right = left # WRONG: Resetting right pointer
max_count = 0 # Also resetting count
while right < n and max_count < k:
# ... rest of logic
Why it's wrong: The two-pointer technique's efficiency relies on the monotonic property - if we need to extend to position p
to get k occurrences starting from index i
, then starting from i+1
, we'll need at least position p
(never less). Resetting destroys this optimization.
Correct approach: Keep the right
pointer where it is and only adjust max_count
when moving the left
pointer forward, maintaining the sliding window property.
What are the most two important steps in writing a depth first search function? (Select 2)
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!