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 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 using first_true_index template
15            # Find first length where score >= k (inverted condition)
16            left, right = 1, end_idx
17            first_true_index = end_idx + 1  # Default: all lengths are valid
18
19            while left <= right:
20                mid = (left + right) // 2
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
25                # Inverted condition: check if score is TOO LARGE (>= k)
26                if subarray_sum * mid >= k:
27                    first_true_index = mid
28                    right = mid - 1
29                else:
30                    left = mid + 1
31
32            # Maximum valid length = first_true_index - 1
33            # All subarrays of length 1, 2, ..., (first_true_index - 1) are valid
34            total_count += first_true_index - 1
35
36        return total_count
37
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 using first_true_index template
16            // Find first length where score >= k (inverted condition)
17            int left = 1;
18            int right = endIndex;
19            int firstTrueIndex = endIndex + 1;  // Default: all lengths are valid
20
21            while (left <= right) {
22                int mid = left + (right - left) / 2;
23
24                // Calculate sum of subarray of length mid ending at endIndex
25                long subarraySum = prefixSum[endIndex] - prefixSum[endIndex - mid];
26
27                // Inverted condition: check if score is TOO LARGE (>= k)
28                if (subarraySum * mid >= k) {
29                    firstTrueIndex = mid;
30                    right = mid - 1;
31                } else {
32                    left = mid + 1;
33                }
34            }
35
36            // Maximum valid length = firstTrueIndex - 1
37            totalCount += firstTrueIndex - 1;
38        }
39
40        return totalCount;
41    }
42}
43
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            // Binary search using first_true_index template
18            // Find first length where score >= k (inverted condition)
19            int left = 1, right = i;
20            int firstTrueIndex = i + 1;  // Default: all lengths are valid
21
22            while (left <= right) {
23                int mid = left + (right - left) / 2;
24
25                // Calculate sum of subarray of length 'mid' ending at position i-1
26                long long subarraySum = prefixSum[i] - prefixSum[i - mid];
27
28                // Inverted condition: check if score is TOO LARGE (>= k)
29                if (subarraySum * mid >= k) {
30                    firstTrueIndex = mid;
31                    right = mid - 1;
32                } else {
33                    left = mid + 1;
34                }
35            }
36
37            // Maximum valid length = firstTrueIndex - 1
38            result += firstTrueIndex - 1;
39        }
40
41        return result;
42    }
43};
44
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        // Binary search using first_true_index template
22        // Find first length where score >= k (inverted condition)
23        let left: number = 1;
24        let right: number = endIndex;
25        let firstTrueIndex: number = endIndex + 1;  // Default: all lengths are valid
26
27        while (left <= right) {
28            const mid: number = Math.floor((left + right) / 2);
29
30            // Calculate subarray sum using prefix sum
31            const subarraySum = prefixSum[endIndex] - prefixSum[endIndex - mid];
32
33            // Inverted condition: check if score is TOO LARGE (>= k)
34            if (subarraySum * mid >= k) {
35                firstTrueIndex = mid;
36                right = mid - 1;
37            } else {
38                left = mid + 1;
39            }
40        }
41
42        // Maximum valid length = firstTrueIndex - 1
43        totalCount += firstTrueIndex - 1;
44    }
45
46    return totalCount;
47}
48

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: Binary Search Using first_true_index Template

For each ending position i, we find the maximum valid length where score < k. This is a "last true" pattern, so we invert the condition: find the first length where score >= k.

Using the first_true_index template:

def find_max_valid_length(end_idx):
    left, right = 1, end_idx
    first_true_index = end_idx + 1  # Default: all lengths are valid

    while left <= right:
        mid = (left + right) // 2
        subarray_sum = prefix_sums[end_idx] - prefix_sums[end_idx - mid]
        if subarray_sum * mid >= k:  # Inverted: score is TOO LARGE
            first_true_index = mid
            right = mid - 1
        else:
            left = mid + 1

    # Maximum valid length = first_true_index - 1
    return first_true_index - 1

Counting Logic:

  • first_true_index gives the first length where score >= k (invalid)
  • first_true_index - 1 gives the maximum length where score < k (valid)
  • All subarrays of length 1, 2, ..., (first_true_index - 1) are valid

Time Complexity: O(n log n) - binary search per position. Space Complexity: O(n) - 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]

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. Using Wrong Binary Search Template

This problem finds the maximum valid length where score < k. The correct approach inverts the condition to use the first_true_index template.

Wrong approach:

left, right = 0, end_idx
while left < right:
    mid = (left + right + 1) >> 1
    if subarray_sum * mid < k:  # Valid
        left = mid
    else:
        right = mid - 1
return left

Correct approach (first_true_index template):

left, right = 1, end_idx
first_true_index = end_idx + 1  # Default: all lengths are valid

while left <= right:
    mid = (left + right) // 2
    if subarray_sum * mid >= k:  # Inverted: find first INVALID length
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index - 1  # Maximum valid length

The key insight is to invert the condition: instead of finding where length IS valid, we find the first length where score >= k (invalid), then return first_true_index - 1.

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 Default for first_true_index

The Pitfall: When no length satisfies score >= k (all lengths are valid), first_true_index should default to end_idx + 1 so that first_true_index - 1 = end_idx gives the correct maximum length.

Wrong: first_true_index = -1 or first_true_index = end_idx Correct: first_true_index = end_idx + 1

This default ensures that when all subarrays are valid, we correctly count all of them.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More