Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 from 0 to i (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 (set l = mid)
    • Otherwise, we need a shorter subarray (set r = mid - 1)
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 Evaluator

Example 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 is O(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 requires O(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 use mid = (left + right) // 2, mid equals left
  • If the condition (s[i] - s[i - mid]) * mid < k is true, we set left = mid, which doesn't change left
  • 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, check sum < 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 index j to i-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 of nums[start:end]
  • The length of this subarray is end - start, not end - 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More