Given an integer array, write a function which returns the sum of values between two indexes.
Input: nums = [3, -7, 6, 0, -2, 5],
[sum_range(0, 3), sum_range(2, 4), sum_range(2, 2)]
Output: [2, 4, 6]
nums contains at least one element.
Each sum_range operation will query a valid range of the input array.
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
To know how much you spent between two dates, you do not dig out every receipt and re-add them. You look at your running balance on each date and subtract. A prefix-sum array is that running balance for the array — compute it once, then every “how much between here and there?” is one subtraction.
We need to code a function sum_range(i, j), where i and j are the indexes defining the boundaries of the range to be summed up.
A naive solution is to iteratively sum the array values from index i to j, which takes linear time for each call to sum_range. Since we have access to the input array before any calls to sum_range are made, we should consider if any preprocessing can be done to improve the efficiency of sum_range.
This problem deals with subarray sums, so it might be useful to think about how prefix sums can be applied to solve it. Consider the integer array below and its prefix sums:
We already notice that the prefix sum array has some use: the prefix sum up to any index j essentially gives the answer to sum_range(0, j). For example, the sum of the range [0, 3] is just the prefix sum up to index 3:
Therefore, when i == 0:
sum_range(0, j) = prefix_sum[j]
What about when the requested range doesn’t start at 0? Let's say we want to find the sum in the range [2, 4]:
Is there a way to get this using only prefix sums? All prefix sum values are sums for ranges that start at index 0. So, let’s see how we could make use of these ranges. Consider the sum of the range [0, 4], which corresponds to prefix_sum[4]:
The key observation here is that the sum of the range [2, 4] can be obtained by subtracting the sum of the range [0, 1] from the sum above. This can be visualized:
Since the sums of ranges [0, 4] and [0, 1] are both values in our prefix sum array, we can obtain the sum of the range [2, 4] from the following expression: prefix_sum[4] - prefix_sum[1].
Therefore, when i > 0:
sum_range(i, j) = prefix_sum[j] - prefix_sum[i - 1]
from typing import List
class SumBetweenRange:
def __init__(self, nums: List[int]):
self.prefix_sum = [nums[0]]
for i in range(1, len(nums)):
self.prefix_sum.append(self.prefix_sum[-1] + nums[i])
def sum_range(self, i: int, j: int) -> int:
if i == 0:
return self.prefix_sum[j]
return self.prefix_sum[j] - self.prefix_sum[i - 1]struct SumBetweenRange {
vector<long long> prefix_sum;
SumBetweenRange(vector<int>& nums) {
prefix_sum.assign(nums.size() + 1, 0);
for (size_t i = 0; i < nums.size(); i++) {
prefix_sum[i + 1] = prefix_sum[i] + nums[i];
}
}
long long sumRange(int i, int j) {
return prefix_sum[j + 1] - prefix_sum[i];
}
};class SumBetweenRange {
long[] prefixSum;
SumBetweenRange(int[] nums) {
prefixSum = new long[nums.length + 1];
for (int i = 0; i < nums.length; i++)
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
long sumRange(int i, int j) { return prefixSum[j + 1] - prefixSum[i]; }
}Time complexity: The time complexity of the constructor is , where denotes the length of the array. This is because we populate a prefix_sum array of length . The time complexity of sum_range is .
Space complexity: The space complexity is due to the space taken up by the prefix_sum array.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
[3, 1, 4, 1, 5], query sum_range(1, 3)Step 1 — build the running total (note the leading 0):
| i | nums[i] | prefix_sum[i+1] |
|---|---|---|
| — | — | prefix_sum[0] = 0 |
| 0 | 3 | 0 + 3 = 3 |
| 1 | 1 | 3 + 1 = 4 |
| 2 | 4 | 4 + 4 = 8 |
| 3 | 1 | 8 + 1 = 9 |
| 4 | 5 | 9 + 5 = 14 |
Step 2 — answer the query. We want nums[1..3], so subtract prefix_sum[1] from prefix_sum[4]:
prefix_sum[j+1] = prefix_sum[4] prefix_sum[i] = prefix_sum[1]
[-2, 5, -1, 3]Build the prefix array — negatives just make it go down instead of up:
| i | nums[i] | prefix_sum[i+1] |
|---|---|---|
| — | — | prefix_sum[0] = 0 |
| 0 | −2 | 0 + (−2) = −2 |
| 1 | 5 | −2 + 5 = 3 |
| 2 | −1 | 3 + (−1) = 2 |
| 3 | 3 | 2 + 3 = 5 |
prefix_sum[j+1] prefix_sum[i]
i = 0, so we subtract prefix_sum[0] = 0. This is exactly why the leading zero exists.
nums[2] = −1.
Take-away: negatives need no special handling, a range starting at 0 subtracts the leading zero, and a single element (i, i) is just prefix_sum[i+1] − prefix_sum[i].
The list self.prefix_sum is sized len(nums) + 1 so index 0 holds the leading zero. Python’s big integers mean no overflow worries.
We store the prefix as vector<long long> so large cumulative sums do not overflow. Everything else mirrors Python one-to-one.
The prefix array is long[] for the same overflow safety. Field prefixSum, method sumRange — identical logic to the other two.