2302. Count Subarrays With Score Less Than K
Problem Description
You are given a positive integer array nums
and an integer k
. The score of an array is calculated by multiplying the sum of its elements by its length.
For example, if we have an array [1, 2, 3, 4, 5]
:
- Sum of elements =
1 + 2 + 3 + 4 + 5 = 15
- Length =
5
- Score =
15 × 5 = 75
Your task is to count how many non-empty subarrays of nums
have a score that is strictly less than k
.
A subarray is a contiguous sequence of elements from the original array. For instance, if nums = [1, 2, 3, 4]
, then [2, 3]
and [1, 2, 3]
are subarrays, but [1, 3]
is not (elements are not contiguous).
The function should return the total count of all such subarrays whose score is less than k
.
Intuition
When we need to count subarrays with a certain property, a brute force approach would be to check every possible subarray. However, this would be inefficient as there are O(n²)
subarrays.
Let's think about the structure of the problem. For a subarray from index i
to j
, the score is (sum of elements) × (length)
. If we fix the ending position j
, we need to find all valid starting positions i
where the score is less than k
.
Notice an important property: as we extend a subarray to the left (decreasing the starting index while keeping the ending fixed), both the sum and the length increase. This means the score increases monotonically. In other words, if a subarray of length L
ending at position j
has a score ≥ k
, then all longer subarrays ending at j
will also have scores ≥ k
.
This monotonic property is perfect for binary search! For each ending position, we can use binary search to find the maximum length l
such that the subarray of length l
ending at that position has a score less than k
. Then all subarrays of length 1, 2, ..., l
ending at that position are valid.
To efficiently calculate the sum of any subarray, we can use prefix sums. With prefix sum array s
, the sum of elements from index i
to j
is simply s[j+1] - s[i]
.
So the approach becomes: for each ending position, binary search for the maximum valid length, and add that length to our answer. The sum of all these lengths gives us the total count of valid subarrays.
Learn more about Binary Search, Prefix Sum and Sliding Window patterns.
Solution Approach
The solution uses prefix sum combined with binary search to efficiently count the valid subarrays.
Step 1: Build Prefix Sum Array
First, we create a prefix sum array s
where s[i]
represents the sum of the first i
elements of nums
. We initialize it with 0
at index 0
for easier calculation. This allows us to compute the sum of any subarray in O(1)
time: the sum from index i
to j
is s[j+1] - s[i]
.
s = list(accumulate(nums, initial=0))
Step 2: Enumerate Ending Positions
We iterate through each position i
(from 1
to len(s)-1
) as the ending position of potential subarrays. Position i
in the prefix sum array corresponds to including the first i
elements of the original array.
Step 3: Binary Search for Maximum Valid Length
For each ending position i
, we need to find the maximum length l
such that a subarray of length l
ending at position i-1
in the original array has a score less than k
.
The binary search works as follows:
- Search range:
l
from0
toi
(the maximum possible length) - For a candidate length
mid
:- The sum of the subarray is
s[i] - s[i - mid]
- The score is
(s[i] - s[i - mid]) * mid
- If score <
k
, we can try a longer subarray (setl = mid
) - Otherwise, we need a shorter subarray (set
r = mid - 1
)
- The sum of the subarray is
while l < r: mid = (l + r + 1) >> 1 # equivalent to (l + r + 1) // 2 if (s[i] - s[i - mid]) * mid < k: l = mid else: r = mid - 1
Step 4: Count Valid Subarrays
After the binary search, l
represents the maximum length where the score is less than k
. This means all subarrays of length 1, 2, ..., l
ending at this position are valid, contributing l
to our answer.
Step 5: Return Total Count
We sum up the counts for all ending positions to get the final answer.
The time complexity is O(n log n)
where n
is the length of the array, as we perform a binary search for each of the n
positions. The space complexity is O(n)
for storing the prefix sum array.
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 = [2, 1, 4, 3, 5]
and k = 10
.
Step 1: Build Prefix Sum Array
- Original array:
[2, 1, 4, 3, 5]
- Prefix sum
s
:[0, 2, 3, 7, 10, 15]
s[0] = 0
(initial value)s[1] = 0 + 2 = 2
s[2] = 2 + 1 = 3
s[3] = 3 + 4 = 7
s[4] = 7 + 3 = 10
s[5] = 10 + 5 = 15
Step 2-4: Process Each Ending Position
Position i = 1 (subarrays ending at index 0 of original array):
- Binary search for max length where score < 10
- Try length 1: sum =
s[1] - s[0] = 2 - 0 = 2
, score =2 × 1 = 2 < 10
✓ - Maximum valid length = 1
- Valid subarrays:
[2]
- Count: 1
Position i = 2 (subarrays ending at index 1):
- Binary search range: length 0 to 2
- Try length 2: sum =
s[2] - s[0] = 3 - 0 = 3
, score =3 × 2 = 6 < 10
✓ - Maximum valid length = 2
- Valid subarrays:
[1]
(length 1),[2,1]
(length 2) - Count: 2
Position i = 3 (subarrays ending at index 2):
- Binary search range: length 0 to 3
- Try length 2: sum =
s[3] - s[1] = 7 - 2 = 5
, score =5 × 2 = 10 ≥ 10
✗ - Try length 1: sum =
s[3] - s[2] = 7 - 3 = 4
, score =4 × 1 = 4 < 10
✓ - Maximum valid length = 1
- Valid subarrays:
[4]
- Count: 1
Position i = 4 (subarrays ending at index 3):
- Binary search range: length 0 to 4
- Try length 2: sum =
s[4] - s[2] = 10 - 3 = 7
, score =7 × 2 = 14 ≥ 10
✗ - Try length 1: sum =
s[4] - s[3] = 10 - 7 = 3
, score =3 × 1 = 3 < 10
✓ - Maximum valid length = 1
- Valid subarrays:
[3]
- Count: 1
Position i = 5 (subarrays ending at index 4):
- Binary search range: length 0 to 5
- Try length 3: sum =
s[5] - s[2] = 15 - 3 = 12
, score =12 × 3 = 36 ≥ 10
✗ - Try length 1: sum =
s[5] - s[4] = 15 - 10 = 5
, score =5 × 1 = 5 < 10
✓ - Maximum valid length = 1
- Valid subarrays:
[5]
- Count: 1
Step 5: Total Count Total = 1 + 2 + 1 + 1 + 1 = 6
The 6 valid subarrays are: [2]
, [1]
, [2,1]
, [4]
, [3]
, [5]
Solution Implementation
1from typing import List
2from itertools import accumulate
3
4class Solution:
5 def countSubarrays(self, nums: List[int], k: int) -> int:
6 # Create prefix sum array with initial 0
7 # prefix_sums[i] represents sum of nums[0:i]
8 prefix_sums = list(accumulate(nums, initial=0))
9
10 total_count = 0
11
12 # For each ending position i (1-indexed in prefix_sums)
13 for end_idx in range(1, len(prefix_sums)):
14 # Binary search for maximum subarray length ending at position end_idx-1
15 # where (subarray_sum * subarray_length) < k
16 left, right = 0, end_idx
17
18 while left < right:
19 # Calculate mid point (biased towards right when even)
20 mid = (left + right + 1) >> 1
21
22 # Calculate sum of subarray with length mid ending at end_idx-1
23 # subarray_sum = prefix_sums[end_idx] - prefix_sums[end_idx - mid]
24 subarray_sum = prefix_sums[end_idx] - prefix_sums[end_idx - mid]
25
26 # Check if sum * length < k
27 if subarray_sum * mid < k:
28 # Can include subarrays of this length, try longer ones
29 left = mid
30 else:
31 # This length is too long, try shorter ones
32 right = mid - 1
33
34 # Add count of valid subarrays ending at current position
35 # left represents the maximum valid length
36 total_count += left
37
38 return total_count
39
1class Solution {
2 public long countSubarrays(int[] nums, long k) {
3 int n = nums.length;
4
5 // Build prefix sum array where prefixSum[i] = sum of nums[0...i-1]
6 long[] prefixSum = new long[n + 1];
7 for (int i = 0; i < n; ++i) {
8 prefixSum[i + 1] = prefixSum[i] + nums[i];
9 }
10
11 long totalCount = 0;
12
13 // For each ending position i (1-indexed), find all valid subarrays ending at position i
14 for (int endIndex = 1; endIndex <= n; ++endIndex) {
15 // Binary search to find the maximum length of subarray ending at endIndex
16 // where (sum of subarray) * (length of subarray) < k
17 int left = 0;
18 int right = endIndex;
19
20 while (left < right) {
21 // Calculate mid point (biased towards right when even)
22 int mid = (left + right + 1) >> 1;
23
24 // Calculate sum of subarray of length mid ending at endIndex
25 long subarraySum = prefixSum[endIndex] - prefixSum[endIndex - mid];
26
27 // Check if score (sum * length) is less than k
28 if (subarraySum * mid < k) {
29 // Valid subarray, try to find longer ones
30 left = mid;
31 } else {
32 // Score is too large, need shorter subarrays
33 right = mid - 1;
34 }
35 }
36
37 // Add the count of valid subarrays ending at current position
38 totalCount += left;
39 }
40
41 return totalCount;
42 }
43}
44
1class Solution {
2public:
3 long long countSubarrays(vector<int>& nums, long long k) {
4 int n = nums.size();
5
6 // Build prefix sum array where prefixSum[i] = sum of nums[0] to nums[i-1]
7 long long prefixSum[n + 1];
8 prefixSum[0] = 0;
9 for (int i = 0; i < n; ++i) {
10 prefixSum[i + 1] = prefixSum[i] + nums[i];
11 }
12
13 long long result = 0;
14
15 // For each ending position i (1-indexed), find the maximum length of valid subarray
16 for (int i = 1; i <= n; ++i) {
17 int left = 0, right = i;
18
19 // Binary search to find the maximum length where sum * length < k
20 while (left < right) {
21 int mid = (left + right + 1) >> 1; // Equivalent to (left + right + 1) / 2
22
23 // Calculate sum of subarray of length 'mid' ending at position i-1
24 long long subarraySum = prefixSum[i] - prefixSum[i - mid];
25
26 // Check if the score (sum * length) is less than k
27 if (subarraySum * mid < k) {
28 left = mid; // Valid length, try to find a larger one
29 } else {
30 right = mid - 1; // Invalid length, reduce the search range
31 }
32 }
33
34 // Add the count of valid subarrays ending at position i-1
35 result += left;
36 }
37
38 return result;
39 }
40};
41
1/**
2 * Counts the number of subarrays whose score is less than k
3 * Score is defined as the sum of elements multiplied by the length of subarray
4 * @param nums - The input array of numbers
5 * @param k - The threshold value for subarray score
6 * @returns The count of valid subarrays
7 */
8function countSubarrays(nums: number[], k: number): number {
9 const arrayLength: number = nums.length;
10
11 // Build prefix sum array where prefixSum[i] represents sum of first i elements
12 const prefixSum: number[] = Array(arrayLength + 1).fill(0);
13 for (let i = 0; i < arrayLength; ++i) {
14 prefixSum[i + 1] = prefixSum[i] + nums[i];
15 }
16
17 let totalCount: number = 0;
18
19 // For each ending position, find the maximum length of valid subarray
20 for (let endIndex = 1; endIndex <= arrayLength; ++endIndex) {
21 let left: number = 0;
22 let right: number = endIndex;
23
24 // Binary search to find the maximum valid subarray length ending at endIndex-1
25 while (left < right) {
26 const mid: number = (left + right + 1) >> 1;
27
28 // Calculate subarray sum using prefix sum and check if score < k
29 // Score = sum * length = (prefixSum[endIndex] - prefixSum[endIndex - mid]) * mid
30 if ((prefixSum[endIndex] - prefixSum[endIndex - mid]) * mid < k) {
31 left = mid;
32 } else {
33 right = mid - 1;
34 }
35 }
36
37 // Add the count of all valid subarrays ending at position endIndex-1
38 totalCount += left;
39 }
40
41 return totalCount;
42}
43
Time and Space Complexity
Time Complexity: O(n × log n)
The algorithm uses a prefix sum array and then iterates through each position i
from 1 to n
(where n
is the length of the array). For each position i
, it performs a binary search to find the maximum length l
of a subarray ending at position i-1
that satisfies the condition (s[i] - s[i - mid]) * mid < k
.
- The outer loop runs
n
times - For each iteration, the binary search performs
O(log i)
operations, which in the worst case isO(log n)
- Therefore, the overall time complexity is
O(n × log n)
Space Complexity: O(n)
The algorithm creates a prefix sum array s
using accumulate
with initial=0
, which has length n + 1
. This is the only additional data structure that scales with the input size. The other variables (ans
, i
, l
, r
, mid
) use constant space.
- The prefix sum array
s
requiresO(n)
space - All other variables use
O(1)
space - Therefore, the overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Binary Search Boundary Calculation Error
The Pitfall:
The most common mistake is using the wrong midpoint calculation in binary search. When searching for the maximum valid length, using mid = (left + right) // 2
instead of mid = (left + right + 1) // 2
will cause an infinite loop when left = right - 1
and the condition is satisfied.
Why it happens:
- When
left = right - 1
and we usemid = (left + right) // 2
, mid equalsleft
- If the condition
(s[i] - s[i - mid]) * mid < k
is true, we setleft = mid
, which doesn't changeleft
- This creates an infinite loop
Solution:
Always use mid = (left + right + 1) // 2
(or (left + right + 1) >> 1
) when searching for the maximum value where we update left = mid
on success.
2. Score Calculation Overflow
The Pitfall:
For large arrays with large values, calculating subarray_sum * length
might cause integer overflow in languages with fixed integer sizes. While Python handles arbitrary precision integers automatically, this is a critical issue in languages like Java or C++.
Solution:
- In Python: No action needed due to automatic handling
- In other languages: Use long/int64 types or check for overflow before multiplication
- Alternative approach: Rearrange the inequality to avoid multiplication: instead of
sum * length < k
, checksum < k / length
(being careful with integer division)
3. Off-by-One Error in Prefix Sum Indexing
The Pitfall:
Confusing the indexing between the original array and the prefix sum array. The prefix sum array has length n+1
where prefix_sums[i]
represents the sum of the first i
elements of the original array.
Why it happens:
prefix_sums[0] = 0
(sum of zero elements)prefix_sums[i] - prefix_sums[j]
gives the sum of elements from indexj
toi-1
in the original array- It's easy to miscalculate which elements are included in the subarray
Solution:
- Always remember that
prefix_sums[end] - prefix_sums[start]
gives the sum ofnums[start:end]
- The length of this subarray is
end - start
, notend - start + 1
- Draw out small examples to verify the indexing
4. Incorrect Initialization of Binary Search Bounds
The Pitfall:
Setting left = 1
instead of left = 0
in the binary search, which would miss counting subarrays of length 0 (though in this problem, we want non-empty subarrays, so length must be at least 1).
Solution:
- Initialize
left = 0
to handle edge cases properly - The binary search will naturally find
left = 0
if no valid subarrays exist - If you need to ensure non-empty subarrays, you can either:
- Add a check after binary search:
total_count += max(0, left)
- Or initialize
left = 1
but handle the case where even length-1 subarray exceeds k
- Add a check after binary search:
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!