Given an integer array nums
and two integers lower
and upper
, return the number of range sums that lie in [lower, upper]
inclusive.
A range sum S(i, j)
is defined as the sum of the elements in nums
between indices i
and j
(i.e., nums[i] + nums[i + 1] + ... + nums[j]
) where 0 <= i <= j < nums.length
.
(i, j)
must satisfy lower <= S(i, j) <= upper
.nums
can be negative, zero, or positive.
The problem asks us to count the number of subarrays whose sums fall within a given range. The simplest way is to try every possible subarray, compute its sum, and check if it falls in the range. This brute-force approach checks all O(n^2)
subarrays, which is too slow for large arrays.
To optimize, we notice that subarray sums can be computed efficiently using prefix sums. The sum from i
to j
is prefix[j + 1] - prefix[i]
. The challenge now is to count, for each prefix[j+1]
, how many previous prefix[i]
values satisfy lower <= prefix[j+1] - prefix[i] <= upper
.
This leads us to techniques like modified merge sort or using Binary Indexed Trees or balanced BSTs for efficient counting.
prefix
where prefix[0] = 0
and prefix[i+1] = prefix[i] + nums[i]
.prefix
array, and during the merge step, count the number of pairs (i, j)
such that lower <= prefix[j] - prefix[i] <= upper
.O(n log n)
.
Suppose nums = [-2, 5, -1]
, lower = -2
, upper = 2
.
prefix = [0, -2, 3, 2]
(i, j)
where i < j
:prefix[1] - prefix[0] = -2 - 0 = -2
(valid)prefix[2] - prefix[0] = 3 - 0 = 3
(invalid)prefix[2] - prefix[1] = 3 - (-2) = 5
(invalid)prefix[3] - prefix[0] = 2 - 0 = 2
(valid)prefix[3] - prefix[1] = 2 - (-2) = 4
(invalid)prefix[3] - prefix[2] = 2 - 3 = -1
(valid)class Solution:
def countRangeSum(self, nums, lower, upper):
def sort(lo, hi):
if hi - lo <= 1:
return 0
mid = (lo + hi) // 2
count = sort(lo, mid) + sort(mid, hi)
j = k = mid
for left in prefix[lo:mid]:
while k < hi and prefix[k] - left < lower:
k += 1
while j < hi and prefix[j] - left <= upper:
j += 1
count += j - k
prefix[lo:hi] = sorted(prefix[lo:hi])
return count
prefix = [0]
for num in nums:
prefix.append(prefix[-1] + num)
return sort(0, len(prefix))
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
vector<long> prefix(nums.size() + 1, 0);
for (int i = 0; i < nums.size(); ++i)
prefix[i + 1] = prefix[i] + nums[i];
return sortCount(prefix, 0, prefix.size(), lower, upper);
}
int sortCount(vector<long>& prefix, int lo, int hi, int lower, int upper) {
if (hi - lo <= 1) return 0;
int mid = (lo + hi) / 2;
int count = sortCount(prefix, lo, mid, lower, upper) + sortCount(prefix, mid, hi, lower, upper);
int j = mid, k = mid;
for (int i = lo; i < mid; ++i) {
while (k < hi && prefix[k] - prefix[i] < lower) ++k;
while (j < hi && prefix[j] - prefix[i] <= upper) ++j;
count += j - k;
}
inplace_merge(prefix.begin() + lo, prefix.begin() + mid, prefix.begin() + hi);
return count;
}
};
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long[] prefix = new long[nums.length + 1];
for (int i = 0; i < nums.length; ++i)
prefix[i + 1] = prefix[i] + nums[i];
return sortCount(prefix, 0, prefix.length, lower, upper);
}
private int sortCount(long[] prefix, int lo, int hi, int lower, int upper) {
if (hi - lo <= 1) return 0;
int mid = (lo + hi) / 2;
int count = sortCount(prefix, lo, mid, lower, upper) + sortCount(prefix, mid, hi, lower, upper);
int j = mid, k = mid;
for (int i = lo; i < mid; ++i) {
while (k < hi && prefix[k] - prefix[i] < lower) ++k;
while (j < hi && prefix[j] - prefix[i] <= upper) ++j;
count += j - k;
}
Arrays.sort(prefix, lo, hi);
return count;
}
}
var countRangeSum = function(nums, lower, upper) {
let prefix = [0];
for (let num of nums) {
prefix.push(prefix[prefix.length - 1] + num);
}
function sort(lo, hi) {
if (hi - lo <= 1) return 0;
let mid = Math.floor((lo + hi) / 2);
let count = sort(lo, mid) + sort(mid, hi);
let j = mid, k = mid;
for (let i = lo; i < mid; ++i) {
while (k < hi && prefix[k] - prefix[i] < lower) k++;
while (j < hi && prefix[j] - prefix[i] <= upper) j++;
count += j - k;
}
let sorted = [];
let l = lo, r = mid;
while (l < mid && r < hi) {
if (prefix[l] < prefix[r]) sorted.push(prefix[l++]);
else sorted.push(prefix[r++]);
}
while (l < mid) sorted.push(prefix[l++]);
while (r < hi) sorted.push(prefix[r++]);
for (let i = 0; i < sorted.length; ++i) {
prefix[lo + i] = sorted[i];
}
return count;
}
return sort(0, prefix.length);
};
O(n^2)
because it checks every possible subarray.O(1)
extra, or O(n)
if using prefix sums for speedup.O(n \log n)
because each divide-and-conquer step processes O(n)
elements over O(\log n)
levels.O(n)
for the prefix sum and temporary arrays used in merge sort.
The problem of counting range sums is efficiently solved by combining prefix sums with a modified merge sort. Prefix sums allow quick calculation of subarray sums, and merge sort enables us to count valid ranges in O(n \log n)
time by leveraging the sorted order. This approach is both elegant and practical, turning a seemingly quadratic problem into an efficient divide-and-conquer solution.