1343. Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold
Problem Description
You are given an array of integers arr
and two integers k
and threshold
. Your task is to find how many contiguous sub-arrays of exactly size k
have an average value that is greater than or equal to threshold
.
A sub-array is a contiguous portion of the original array. For example, if arr = [1, 2, 3, 4]
and k = 2
, the sub-arrays of size 2 would be [1, 2]
, [2, 3]
, and [3, 4]
.
To find the average of a sub-array, you sum all its elements and divide by k
. If this average is greater than or equal to threshold
, that sub-array counts toward your answer.
The solution uses a sliding window technique to efficiently calculate this. Instead of repeatedly calculating averages, it multiplies threshold
by k
at the beginning. This allows direct comparison of the window's sum with threshold * k
, avoiding division operations.
The algorithm works by:
- First calculating the sum of the initial window of size
k
- Sliding the window one position at a time by removing the leftmost element and adding the new rightmost element
- Checking if each window's sum meets or exceeds
threshold * k
- Counting all windows that satisfy the condition
For example, with arr = [2, 2, 2, 2, 5, 5, 5, 8]
, k = 3
, and threshold = 4
:
- The sub-arrays of size 3 are:
[2,2,2]
,[2,2,2]
,[2,2,5]
,[2,5,5]
,[5,5,5]
,[5,5,8]
- Their averages are: 2, 2, 3, 4, 5, 6
- Four of these (4, 5, 6) are >= 4, so the answer is 4
Intuition
The naive approach would be to check every possible sub-array of size k
by calculating its sum and then dividing by k
to get the average. However, this involves redundant calculations since consecutive windows overlap significantly.
Consider two consecutive windows: one starting at index i
and another at index i+1
. These windows share k-1
elements. When we move from the first window to the second, we're essentially removing one element from the left and adding one element from the right. This observation leads us to the sliding window technique.
Instead of recalculating the entire sum for each window, we can maintain a running sum and update it by:
- Subtracting the element that's leaving the window (the leftmost element of the previous window)
- Adding the element that's entering the window (the new rightmost element)
This reduces our work from O(k)
per window to O(1)
per window.
Another key insight is about the comparison with the threshold. The condition "average >= threshold" can be rewritten as "sum >= threshold * k". By multiplying the threshold by k
once at the beginning, we avoid performing division for each window. This is both more efficient and avoids potential floating-point precision issues.
The pattern s += arr[i] - arr[i - k]
elegantly captures the sliding window update: arr[i]
is the new element entering the window, and arr[i - k]
is the element leaving the window that's exactly k
positions behind.
This approach transforms what could be an O(n * k)
solution into an O(n)
solution, where n
is the length of the array.
Learn more about Sliding Window patterns.
Solution Approach
The solution implements a sliding window technique to efficiently count sub-arrays of size k
with average greater than or equal to threshold
.
Step 1: Transform the comparison
threshold *= k
We multiply threshold
by k
to transform the average comparison into a sum comparison. This allows us to check if sum >= threshold * k
instead of sum/k >= threshold
, avoiding division operations.
Step 2: Initialize the first window
s = sum(arr[:k])
ans = int(s >= threshold)
We calculate the sum of the first k
elements to establish our initial window. The expression int(s >= threshold)
converts the boolean result to 1 if the condition is met, or 0 otherwise. This counts whether the first window satisfies our requirement.
Step 3: Slide the window through the array
for i in range(k, len(arr)):
s += arr[i] - arr[i - k]
ans += int(s >= threshold)
Starting from index k
, we slide the window one position at a time:
arr[i]
is the new element entering the window on the rightarr[i - k]
is the element leaving the window on the left- The difference updates our running sum
s
in constant time
For each new window position, we check if the sum meets our threshold and increment the answer accordingly.
Time Complexity: O(n)
where n
is the length of the array. We visit each element exactly once after the initial window setup.
Space Complexity: O(1)
as we only use a constant amount of extra space for variables s
and ans
.
The elegance of this solution lies in maintaining a running sum that's updated incrementally, rather than recalculating the entire sum for each window. This pattern is particularly useful for problems involving consecutive sub-arrays of fixed size.
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 a small example with arr = [1, 3, 2, 6, 2]
, k = 3
, and threshold = 3
.
Step 1: Transform the threshold
- Original condition: average ≥ 3
- Multiply threshold by k:
threshold = 3 * 3 = 9
- New condition: sum ≥ 9
Step 2: Initialize the first window
- First window:
[1, 3, 2]
- Sum:
s = 1 + 3 + 2 = 6
- Check: Is 6 ≥ 9? No
ans = 0
Step 3: Slide the window
Window 2 (i = 3):
- Remove
arr[0] = 1
, addarr[3] = 6
- New window:
[3, 2, 6]
- Update sum:
s = 6 + 6 - 1 = 11
- Check: Is 11 ≥ 9? Yes
ans = 0 + 1 = 1
Window 3 (i = 4):
- Remove
arr[1] = 3
, addarr[4] = 2
- New window:
[2, 6, 2]
- Update sum:
s = 11 + 2 - 3 = 10
- Check: Is 10 ≥ 9? Yes
ans = 1 + 1 = 2
Result: 2 sub-arrays have average ≥ 3
The sliding window efficiently updates the sum by removing the element that exits (arr[i-k]
) and adding the element that enters (arr[i]
), avoiding recalculation of the entire window sum each time.
Solution Implementation
1class Solution:
2 def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
3 # Convert threshold to total sum threshold for the subarray
4 # Instead of checking average >= threshold, we check sum >= threshold * k
5 total_threshold = threshold * k
6
7 # Calculate the sum of the first window of size k
8 window_sum = sum(arr[:k])
9
10 # Initialize count with 1 if first window meets threshold, 0 otherwise
11 count = int(window_sum >= total_threshold)
12
13 # Slide the window through the rest of the array
14 for i in range(k, len(arr)):
15 # Update window sum by adding new element and removing the leftmost element
16 window_sum += arr[i] - arr[i - k]
17
18 # Increment count if current window sum meets the threshold
19 count += int(window_sum >= total_threshold)
20
21 return count
22
1class Solution {
2 public int numOfSubarrays(int[] arr, int k, int threshold) {
3 // Convert threshold to total sum threshold (avoid repeated multiplication)
4 int totalThreshold = threshold * k;
5
6 // Calculate the sum of the first window of size k
7 int windowSum = 0;
8 for (int i = 0; i < k; i++) {
9 windowSum += arr[i];
10 }
11
12 // Check if the first window meets the threshold
13 int count = (windowSum >= totalThreshold) ? 1 : 0;
14
15 // Use sliding window technique to check remaining subarrays
16 for (int i = k; i < arr.length; i++) {
17 // Remove the leftmost element of previous window and add the new rightmost element
18 windowSum = windowSum + arr[i] - arr[i - k];
19
20 // Increment count if current window sum meets the threshold
21 count += (windowSum >= totalThreshold) ? 1 : 0;
22 }
23
24 return count;
25 }
26}
27
1class Solution {
2public:
3 int numOfSubarrays(vector<int>& arr, int k, int threshold) {
4 // Convert threshold from average to sum by multiplying with k
5 // This avoids division in each iteration
6 int targetSum = threshold * k;
7
8 // Calculate the sum of the first window of size k
9 int windowSum = accumulate(arr.begin(), arr.begin() + k, 0);
10
11 // Check if the first window meets the threshold
12 int count = (windowSum >= targetSum) ? 1 : 0;
13
14 // Slide the window through the rest of the array
15 for (int i = k; i < arr.size(); ++i) {
16 // Update window sum: add new element, remove the leftmost element
17 windowSum += arr[i] - arr[i - k];
18
19 // Increment count if current window sum meets the threshold
20 if (windowSum >= targetSum) {
21 count++;
22 }
23 }
24
25 return count;
26 }
27};
28
1/**
2 * Counts the number of subarrays of size k whose average is greater than or equal to threshold
3 * @param arr - The input array of numbers
4 * @param k - The size of each subarray
5 * @param threshold - The minimum average value for a valid subarray
6 * @returns The count of valid subarrays
7 */
8function numOfSubarrays(arr: number[], k: number, threshold: number): number {
9 // Convert threshold to sum threshold (multiply by k to avoid division)
10 const sumThreshold: number = threshold * k;
11
12 // Calculate the sum of the first window of size k
13 let windowSum: number = arr.slice(0, k).reduce((accumulator, current) => accumulator + current, 0);
14
15 // Check if the first window meets the threshold
16 let validSubarrayCount: number = windowSum >= sumThreshold ? 1 : 0;
17
18 // Slide the window through the rest of the array
19 for (let i: number = k; i < arr.length; i++) {
20 // Update window sum by adding new element and removing the leftmost element
21 windowSum += arr[i] - arr[i - k];
22
23 // Increment count if current window sum meets the threshold
24 validSubarrayCount += windowSum >= sumThreshold ? 1 : 0;
25 }
26
27 return validSubarrayCount;
28}
29
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array arr
. The algorithm first calculates the sum of the initial window of size k
in O(k)
time. Then it slides the window through the remaining n - k
elements, performing constant time operations (adding one element and removing one element) for each position. Therefore, the total time is O(k) + O(n - k) = O(n)
.
The space complexity is O(1)
. The algorithm only uses a fixed amount of extra space for variables s
(to store the current window sum), ans
(to count valid subarrays), and i
(loop variable), regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Threshold Multiplication
Pitfall: When multiplying threshold * k
, the result might exceed integer limits in some programming languages, especially when both values are large.
Example scenario:
threshold = 10^9
,k = 10^5
threshold * k = 10^14
(may cause overflow in 32-bit integers)
Solution: Instead of multiplying the threshold, keep it as is and perform floating-point division:
# Instead of:
total_threshold = threshold * k
count = int(window_sum >= total_threshold)
# Use:
count = int(window_sum / k >= threshold)
Or use integer division with careful rounding:
count = int(window_sum >= threshold * k) # Ensure using 64-bit integers
2. Off-by-One Error in Window Sliding
Pitfall: Incorrectly calculating which element to remove when sliding the window, often using arr[i - k + 1]
or arr[i - k - 1]
instead of the correct arr[i - k]
.
Example of wrong implementation:
# WRONG - removes wrong element
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k + 1] # This is incorrect!
Solution: Visualize the window positions clearly:
- When
i = k
, we want to removearr[0]
and addarr[k]
- The element to remove is always at index
i - k
3. Handling Edge Cases with Small Arrays
Pitfall: Not handling cases where the array length is less than k
or exactly equals k
.
Example scenario:
arr = [1, 2]
,k = 3
- No valid subarray existsarr = [1, 2, 3]
,k = 3
- Only one subarray exists
Solution: Add validation at the beginning:
def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
if len(arr) < k:
return 0
# Rest of the implementation...
4. Incorrect Initial Window Calculation
Pitfall: Starting the sliding window loop from the wrong index or miscounting the first window.
Example of wrong implementation:
# WRONG - doesn't check the first window
window_sum = sum(arr[:k])
count = 0 # Missing the check for first window!
for i in range(k, len(arr)):
# ...
Solution: Always check if the first window meets the threshold:
window_sum = sum(arr[:k])
count = int(window_sum >= total_threshold) # Check first window
5. Using Division Instead of Multiplication
Pitfall: Performing division in each iteration instead of transforming the comparison once.
Inefficient approach:
# INEFFICIENT - performs division in every iteration
for i in range(k, len(arr)):
window_sum += arr[i] - arr[i - k]
if window_sum / k >= threshold: # Division in each iteration
count += 1
Solution: Transform the comparison once at the beginning to avoid repeated divisions, which are computationally more expensive than comparisons.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
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!