Range Sum of Sorted Subarray Sums
MedPrompt
You are given the array nums consisting of n positive integers. You computed the sum of all non-empty continuous subarrays from the array and then sorted them in non-decreasing order, creating a new array of n * (n + 1) / 2 numbers.
Return the sum of the numbers from index left to index right (indexed from 1), inclusive, in the new array. Since the answer can be a huge number return it modulo 109 + 7.
Example 1:
Input: nums = [1,2,3,4], n = 4, left = 1, right = 5
Output: 13
Explanation: All subarray sums are 1, 3, 6, 10, 2, 5, 9, 3, 7, 4. After sorting them in non-decreasing order we have the new array [1, 2, 3, 3, 4, 5, 6, 7, 9, 10]. The sum of the numbers from index le = 1 to ri = 5 is 1 + 2 + 3 + 3 + 4 = 13. Example 2:
Input: nums = [1,2,3,4], n = 4, left = 3, right = 4
Output: 6
Explanation: The given array is the same as example 1. We have the new array [1, 2, 3, 3, 4, 5, 6, 7, 9, 10]. The sum of the numbers from index le = 3 to ri = 4 is 3 + 3 = 6.Example 3:
Input: nums = [1,2,3,4], n = 4, left = 1, right = 10
Output: 50
Constraints:
n == nums.length1 <= nums.length <= 10001 <= nums[i] <= 1001 <= left <= right <= n * (n + 1) / 2
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the process described in the problem. First, we generate all possible non-empty continuous subarray sums. Then, we sort these sums in non-decreasing order. Finally, we iterate through the sorted list from the left-th to the right-th element (1-indexed) and accumulate their sum.
Algorithm
- Create an empty list, say
sums, to store all subarray sums. - Iterate through the input array
numswith a starting indexifrom0ton-1. - For each
i, start a nested loop with an ending indexjfromiton-1. - Maintain a
currentSumfor the subarray starting ati. In the inner loop, addnums[j]tocurrentSumand add thiscurrentSumto thesumslist. - After generating all
n * (n + 1) / 2sums, sort thesumslist in non-decreasing order. - Initialize a variable
resultto 0. - Iterate from
k = left - 1toright - 1(using 0-based indexing for the list). - Add
sums.get(k)toresult, taking the modulo10^9 + 7at each step to prevent overflow. - Return
result.
Walkthrough
The simplest way to solve this problem is to follow the instructions literally. We can use two nested loops to generate every possible continuous subarray. The outer loop picks the starting element, and the inner loop expands the subarray to the right. For each subarray, we calculate its sum and store it in a list. After generating all n * (n + 1) / 2 sums, we sort this list. Finally, we sum up the elements from index left - 1 to right - 1 in the sorted list, applying the modulo operation at each addition to handle potentially large sums.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public int rangeSum(int[] nums, int n, int left, int right) { int MOD = 1_000_000_007; List<Integer> sums = new ArrayList<>(); for (int i = 0; i < n; i++) { int currentSum = 0; for (int j = i; j < n; j++) { currentSum += nums[j]; sums.add(currentSum); } } Collections.sort(sums); long result = 0; for (int i = left - 1; i < right; i++) { result = (result + sums.get(i)) % MOD; } return (int) result; }}Complexity
Time
O(n^2 log n) - Generating all subarray sums takes O(n^2). The number of sums is `N = n * (n + 1) / 2`, which is O(n^2). Sorting these `N` sums takes O(N log N) = O(n^2 log(n^2)) = O(n^2 log n). Summing the required range takes O(right - left), which is at most O(n^2). The sorting step dominates the complexity.
Space
O(n^2) - We need to store all `n * (n + 1) / 2` subarray sums in a list.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem statement.
Cons
Inefficient in both time and space, especially for large
n.May result in Time Limit Exceeded or Memory Limit Exceeded for larger constraints.
This approach avoids generating and storing all subarray sums at once, which improves space complexity. We can treat the problem as finding elements in a merged list of n sorted lists. Each of these n lists corresponds to the subarray sums starting at a particular index i. Since all numbers in nums are positive, for a fixed start i, the sums sum(i,i), sum(i,i+1), ... form a sorted sequence. A min-heap is a perfect data structure to efficiently extract the smallest element from these n lists in sorted order.
Algorithm
- The problem can be viewed as merging
nsorted lists, where each listicontains subarray sums starting at indexi(sum(i,i), sum(i,i+1), ...). - Initialize a min-priority queue to store tuples of
(sum, startIndex, endIndex). - Initially, populate the heap with the first sum from each of the
nconceptual lists. For eachifrom0ton-1, push(nums[i], i, i)into the heap. - Initialize
result = 0. - Loop from
k = 1toright:- Extract the minimum element
(currentSum, startIndex, endIndex)from the heap. - If
kis within the range[left, right], addcurrentSumtoresult(modulo10^9 + 7). - If the subarray can be extended (i.e.,
endIndex + 1 < n), calculate the next sum forstartIndex:newSum = currentSum + nums[endIndex + 1]. Push(newSum, startIndex, endIndex + 1)into the heap.
- Extract the minimum element
- Return
result.
Walkthrough
We use a min-priority queue to keep track of the smallest current subarray sum from each possible starting position. The heap will store an array or object representing (sum, startIndex, endIndex).
We begin by inserting the smallest sum for each starting index i (which is just nums[i]) into the heap. This means we push (nums[i], i, i) for all i from 0 to n-1.
Then, we iterate right times. In each iteration, we extract the minimum sum from the heap. If the iteration count is between left and right, we add this sum to our total result. After extracting a sum sum(i, j), we generate the next sum from the same starting position i, which is sum(i, j+1), and add it back to the heap. This process is repeated until we have found the right-th smallest sum.
import java.util.PriorityQueue; class Solution { public int rangeSum(int[] nums, int n, int left, int right) { int MOD = 1_000_000_007; // Min-heap stores {sum, startIndex, endIndex} PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> Long.compare(a[0], b[0])); // Initial population: sums of subarrays of length 1 for (int i = 0; i < n; i++) { pq.offer(new long[]{nums[i], i, i}); } long result = 0; for (int i = 1; i <= right; i++) { long[] top = pq.poll(); long sum = top[0]; int endIndex = (int) top[2]; if (i >= left) { result = (result + sum) % MOD; } if (endIndex + 1 < n) { long newSum = sum + nums[endIndex + 1]; pq.offer(new long[]{newSum, top[1], endIndex + 1}); } } return (int) result; }}Complexity
Time
O(right * log n) - Initializing the heap takes O(n log n). The main loop runs `right` times, and each iteration involves heap operations (poll and offer) which take O(log n) time. The total time is O(n log n + right * log n). In the worst case, `right` is O(n^2), making the complexity O(n^2 log n).
Space
O(n) - The priority queue stores at most `n` elements, one for each possible starting index.
Trade-offs
Pros
Much more space-efficient (O(n)) than the brute-force approach (O(n^2)).
Can be faster in practice if
rightis significantly smaller thann^2.
Cons
The worst-case time complexity is still high, similar to the brute-force approach.
The logic is more complex to implement than simple generation and sorting.
This optimal approach reframes the problem. Instead of finding the sum of a range [left, right], we calculate sum_up_to(right) - sum_up_to(left - 1). This reduces the problem to creating a function calculateSumFirstK(k) that finds the sum of the k smallest subarray sums.
This subproblem can be solved efficiently. We don't need to generate the sums. Instead, we can binary search for the value of the k-th smallest sum. For any given value x, we can count how many subarray sums are less than or equal to x in O(n) time using a sliding window. This allows us to find the k-th sum's value, kthSumVal, in O(n * log(max_sum)) time. Once we have kthSumVal, we can calculate the total sum of all subarrays with sums less than kthSumVal, and then add the remaining kthSumVals to reach the k elements.
Algorithm
- The problem can be rephrased as
sum_up_to(right) - sum_up_to(left - 1). - Implement a function
calculateSumFirstK(k)to find the sum of theksmallest subarray sums. - Inside
calculateSumFirstK(k):- Find k-th sum value: Use binary search on the possible range of sums. For a
guess_sum, we need a helpercountLeq(value)that counts subarray sums<= valuein O(n) time using a sliding window. This binary search finds the value of thek-th smallest sum, let's call itkthSumVal. - Calculate total sum: We need the sum of all subarray sums that are strictly less than
kthSumVal, and their count. This can be done with another O(n) helper,getSumAndCountLeq(value), which uses a sliding window and prefix sums on prefix sums for O(1) sum calculation within the window. - Let
(sumSmaller, countSmaller) = getSumAndCountLeq(kthSumVal - 1). - The total sum for the first
kelements is(sumSmaller + (k - countSmaller) * kthSumVal) % MOD.
- Find k-th sum value: Use binary search on the possible range of sums. For a
- The final answer is
(calculateSumFirstK(right) - calculateSumFirstK(left - 1) + MOD) % MOD.
Walkthrough
The core idea is to build a function calculateSumFirstK(nums, n, k) which is called for k = right and k = left - 1.
-
Find the k-th sum's value: We binary search for this value. The range of possible sums is from
1to the total sum ofnums. For amidvalue in our binary search, we need to check if it's a potential candidate for the k-th sum. We do this by counting how many subarray sums are less than or equal tomid. This count can be found in O(n) using a two-pointer/sliding window approach. Based on this count, we adjust our binary search range. This process gives uskthSumValin O(n * log(max_sum)) time. -
Calculate the final sum: Now that we have
kthSumVal, we need the sum of theksmallest elements. We first find the sum of all elements strictly smaller thankthSumVal. This requires a helper function that, for a given valuev, returns both the count and sum of subarray sums less than or equal tov. This can be done in O(n) using a sliding window combined with prefix sums of prefix sums to calculate the sum of sums within the window in O(1) time. Let this give ussumSmallerandcountSmallerforv = kthSumVal - 1. The final sum issumSmaller + (k - countSmaller) * kthSumVal.
class Solution { int MOD = 1_000_000_007; public int rangeSum(int[] nums, int n, int left, int right) { long sumRight = calculateSumFirstK(nums, n, right); long sumLeft = calculateSumFirstK(nums, n, left - 1); return (int) ((sumRight - sumLeft + MOD) % MOD); } private long calculateSumFirstK(int[] nums, int n, int k) { if (k == 0) return 0; int low = 1, high = 0; for (int num : nums) high += num; int kthSumVal = 0; while (low <= high) { int mid = low + (high - low) / 2; if (countSubarraysWithSumLeq(nums, n, mid) >= k) { kthSumVal = mid; high = mid - 1; } else { low = mid + 1; } } long[] res = getSumAndCountLeq(nums, n, kthSumVal - 1); long sumSmaller = res[0]; long countSmaller = res[1]; long totalSum = sumSmaller; long remainingCount = k - countSmaller; totalSum = (totalSum + remainingCount * kthSumVal) % MOD; return totalSum; } private long countSubarraysWithSumLeq(int[] nums, int n, int val) { long count = 0; long currentSum = 0; int left = 0; for (int right = 0; right < n; right++) { currentSum += nums[right]; while (currentSum > val) { currentSum -= nums[left]; left++; } count += (right - left + 1); } return count; } private long[] getSumAndCountLeq(int[] nums, int n, int val) { long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i]; long[] prefixOfPrefix = new long[n + 2]; for (int i = 0; i <= n; i++) prefixOfPrefix[i + 1] = prefixOfPrefix[i] + prefix[i]; long totalSum = 0; long count = 0; int left = 0; for (int right = 0; right < n; right++) { while (prefix[right + 1] - prefix[left] > val) { left++; } int numSubarrays = right - left + 1; count += numSubarrays; long sumOfPrefixes = prefixOfPrefix[right + 1] - prefixOfPrefix[left]; totalSum = (totalSum + (long)numSubarrays * prefix[right + 1] - sumOfPrefixes); } return new long[]{totalSum % MOD, count}; }}Complexity
Time
O(n * log(max_sum)) - The main function `calculateSumFirstK` is called twice. Its complexity is dominated by the binary search, which performs `log(max_sum)` iterations. Each iteration involves a call to an O(n) helper function. `max_sum` is the sum of all elements in `nums`.
Space
O(n) - We need O(n) space for the prefix sum and prefix-of-prefix-sum arrays.
Trade-offs
Pros
Highly efficient with optimal time complexity.
Scales well for large inputs.
Space-efficient, using only O(n) extra space.
Cons
The algorithm is significantly more complex to understand and implement correctly.
Requires careful handling of multiple concepts: binary search, sliding window, and advanced prefix sums.
Solutions
Solution
class Solution {public int rangeSum(int[] nums, int n, int left, int right) { int[] arr = new int[n * (n + 1) / 2]; for (int i = 0, k = 0; i < n; ++i) { int s = 0; for (int j = i; j < n; ++j) { s += nums[j]; arr[k++] = s; } } Arrays.sort(arr); int ans = 0; final int mod = (int)1 e9 + 7; for (int i = left - 1; i < right; ++i) { ans = (ans + arr[i]) % mod; } return ans; }}function rangeSum ( nums , n , left , right ) { let arr = Array (( n * ( n + 1 )) / 2 ). fill ( 0 ); const mod = 10 ** 9 + 7 ; for ( let i = 0 , s = 0 , k = 0 ; i < n ; i ++ , s = 0 ) { for ( let j = i ; j < n ; j ++ , k ++ ) { s += nums [ j ]; arr [ k ] = s ; } } let ans = 0 ; arr = arr . sort (( a , b ) => a - b ). slice ( left - 1 , right ); for ( const x of arr ) { ans += x ; } return ans % mod ; }class Solution: def rangeSum(self, nums: List[int], n: int, left: int, right: int) -> int: arr = [] for i in range(n): s = 0 for j in range(i, n): s += nums[j] arr . append(s) arr . sort() mod = 10 ** 9 + 7 return sum(arr[left - 1: right]) % modclass Solution {public: int rangeSum(vector<int> &nums, int n, int left, int right) { int arr[n * (n + 1) / 2]; for (int i = 0, k = 0; i < n; ++i) { int s = 0; for (int j = i; j < n; ++j) { s += nums[j]; arr[k++] = s; } } sort(arr, arr + n * (n + 1) / 2); int ans = 0; const int mod = 1e9 + 7; for (int i = left - 1; i < right; ++i) { ans = (ans + arr[i]) % mod; } return ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.