The problem
You are given an integer array nums that never changes. Build a class that answers many queries of the form sumRange(i, j): the sum of nums[i] through nums[j], both ends included.
nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1 (-2 + 0 + 3)
sumRange(2, 5) -> -1 (3 - 5 + 2 - 1)
sumRange(0, 5) -> -3 (the whole array)How to think about it
Start with brute force. For each query, loop from i to j and add. That is O(n) per query. With up to 10⁴ queries on an array of 10⁴ numbers, you re-add the same elements again and again, around 10⁸ operations.
The key insight: the array is immutable, so precompute once. Build pre[k] = the sum of the first k elements, with pre[0] = 0. Then the sum of nums[i..j] is everything up to j minus everything before i: pre[j+1] - pre[i]. That is a single subtraction, O(1), no matter how long the range is.
Why the extra leading zero? It removes the special case for i = 0. Without it you would need if i == 0: return pre[j]. With it, one formula covers every query.
Which pattern is this? Prefix sums. The tell is “many sum queries over ranges of a fixed array”. Pay a one-time O(n) cost so every query becomes O(1).
Approach
- In the constructor, start
pre = [0]. - For each
xinnums, appendpre[-1] + x. Nowprehasn + 1entries andpre[k]is the sum ofnums[0..k-1]. - For
sumRange(i, j), returnpre[j+1] - pre[i].
Solution
class NumArray:
def __init__(self, nums):
self.pre = [0] # pre[0] = 0: empty prefix
for x in nums:
self.pre.append(self.pre[-1] + x) # pre[k] = sum of first k elements
def sumRange(self, i, j):
return self.pre[j+1] - self.pre[i] # up to j, minus everything before i
# na = NumArray([-2,0,3,-5,2,-1])
# [na.sumRange(0,2), na.sumRange(2,5), na.sumRange(0,5)] -> [1, -1, -3]Dry run
Building pre for nums = [-2, 0, 3, -5, 2, -1]:
x appended |
New pre[-1] |
pre so far |
|---|---|---|
| -2 | -2 | [0, -2] |
| 0 | -2 | [0, -2, -2] |
| 3 | 1 | [0, -2, -2, 1] |
| -5 | -4 | [0, -2, -2, 1, -4] |
| 2 | -2 | [0, -2, -2, 1, -4, -2] |
| -1 | -3 | [0, -2, -2, 1, -4, -2, -3] |
Then the queries:
| Query | Formula | Result |
|---|---|---|
sumRange(0, 2) |
pre[3] - pre[0] = 1 - 0 |
1 |
sumRange(2, 5) |
pre[6] - pre[2] = -3 - (-2) |
-1 |
sumRange(0, 5) |
pre[6] - pre[0] = -3 - 0 |
-3 |
Complexity
- Time: O(n) to build, O(1) per query. The constructor walks the array once; each query reads two list entries and subtracts.
- Space: O(n). The prefix array holds
n + 1integers.
Edge cases and pitfalls
- Single-element range (
i == j) works:pre[i+1] - pre[i]is justnums[i]. - Off-by-one is the classic bug. With the leading zero, the formula is
pre[j+1] - pre[i], notpre[j] - pre[i]and notpre[j] - pre[i-1]. - Negative numbers are fine; prefix sums do not need non-negative input (unlike a sliding window).
- If the array could change, prefix sums break, because one update shifts every later prefix. That is LeetCode 307, which needs a Fenwick tree or segment tree.
Related problems
- Range Sum Query 2D - Immutable (LeetCode 304): the same idea extended to rectangles in a matrix.
- Find Pivot Index (LeetCode 724): compares the prefix sum on each side of an index.
- Subarray Sum Equals K (LeetCode 560): prefix sums plus a hash map to count ranges with a target sum.
- 1.Range Sum Query - Immutable (LeetCode 303)
- 2.Find Pivot Index (LeetCode 724)
- 3.Minimum Value to Get Positive Step by Step Sum (LeetCode 1413)
- 4.Subarray Sum Equals K (LeetCode 560)
- 5.Product of Array Except Self (LeetCode 238)
- 6.Contiguous Array (LeetCode 525)
- 7.Subarray Sums Divisible by K (LeetCode 974)
- 8.Continuous Subarray Sum (LeetCode 523)
- 9.Range Sum Query 2D - Immutable (LeetCode 304)
- 10.Maximum Size Subarray Sum Equals k (LeetCode 325)