303. Range Sum Query - Immutable
Problem Description
You need to design a data structure that can efficiently calculate the sum of elements in a given range of an integer array.
The problem asks you to implement a class called NumArray
with the following functionality:
-
Constructor
NumArray(int[] nums)
: Takes an integer arraynums
as input and initializes the data structure with this array. -
Method
sumRange(int left, int right)
: Returns the sum of all elements from indexleft
to indexright
(inclusive). In other words, it calculatesnums[left] + nums[left + 1] + ... + nums[right]
.
The key challenge is that multiple queries will be made using the sumRange
method, so you need an efficient way to handle these repeated range sum queries rather than recalculating the sum from scratch each time.
Example usage:
- First, create a
NumArray
object with an array like[1, 3, 5, 7, 9]
- Then call
sumRange(1, 3)
which should return3 + 5 + 7 = 15
- Call
sumRange(0, 2)
which should return1 + 3 + 5 = 9
The solution uses a prefix sum technique where we precompute cumulative sums during initialization. This allows each sumRange
query to be answered in constant time O(1)
by using the formula: prefix_sum[right + 1] - prefix_sum[left]
.
Intuition
When we need to calculate range sums multiple times, the naive approach would be to iterate through the array from index left
to right
for each query, which takes O(n)
time per query. If we have many queries, this becomes inefficient.
The key insight is that we can preprocess the array once to make all future queries fast. Think about how cumulative sums work: if we know the sum of elements from the start of the array up to any position, we can use this information to quickly find the sum of any subrange.
Consider an array [2, 4, 6, 8]
. If we create a prefix sum array that stores cumulative sums from the beginning:
s[0] = 0
(sum of no elements)s[1] = 2
(sum up to index 0)s[2] = 6
(sum up to index 1)s[3] = 12
(sum up to index 2)s[4] = 20
(sum up to index 3)
Now, to find the sum between indices 1 and 2 (which is 4 + 6 = 10
), we can observe that:
- The sum up to index 2 is
s[3] = 12
- The sum up to index 0 is
s[1] = 2
- The difference
12 - 2 = 10
gives us exactly the sum we want!
This works because s[right + 1]
contains the sum of all elements from index 0 to right
, and s[left]
contains the sum of all elements from index 0 to left - 1
. When we subtract them, the elements before index left
cancel out, leaving us with just the sum from left
to right
.
By spending O(n)
time upfront to build the prefix sum array, we can answer each subsequent range query in just O(1)
time using simple subtraction.
Solution Approach
The solution implements the prefix sum technique to efficiently handle multiple range sum queries.
Data Structure:
We use a prefix sum array s
where s[i]
stores the sum of the first i
elements from the original array. The array has length n + 1
where n
is the length of the input array.
Implementation Details:
-
Initialization (
__init__
method):- We use Python's
accumulate
function from theitertools
module to build the prefix sum array - The
initial=0
parameter ensures our prefix sum array starts with 0, representing the sum of zero elements - For an input array
nums = [a, b, c, d]
, the prefix sum array becomess = [0, a, a+b, a+b+c, a+b+c+d]
- This preprocessing takes
O(n)
time andO(n)
space
- We use Python's
-
Range Sum Query (
sumRange
method):- To find the sum between indices
left
andright
(inclusive), we use the formula:s[right + 1] - s[left]
- Why this works:
s[right + 1]
= sum of elements from index 0 toright
s[left]
= sum of elements from index 0 toleft - 1
- Subtracting these gives us the sum from index
left
toright
- Each query is answered in
O(1)
time
- To find the sum between indices
Example Walkthrough:
For nums = [1, 3, 5, 7, 9]
:
- Prefix sum array:
s = [0, 1, 4, 9, 16, 25]
- Query
sumRange(1, 3)
: returnss[4] - s[1] = 16 - 1 = 15
- Query
sumRange(0, 2)
: returnss[3] - s[0] = 9 - 0 = 9
This approach trades additional space for query speed, making it ideal when we need to perform many range sum queries on the same array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example to see how the prefix sum technique works.
Given array: nums = [3, 5, 2, 8, 4]
Step 1: Build the prefix sum array during initialization
We create a prefix sum array s
with length 6 (one more than the original array):
s[0] = 0
(sum of no elements)s[1] = 0 + 3 = 3
(sum up to index 0)s[2] = 3 + 5 = 8
(sum up to index 1)s[3] = 8 + 2 = 10
(sum up to index 2)s[4] = 10 + 8 = 18
(sum up to index 3)s[5] = 18 + 4 = 22
(sum up to index 4)
So our prefix sum array is: s = [0, 3, 8, 10, 18, 22]
Step 2: Process range sum queries
Query 1: sumRange(1, 3)
- Find sum of elements from index 1 to 3
- We need:
nums[1] + nums[2] + nums[3] = 5 + 2 + 8 = 15
- Using our formula:
s[right + 1] - s[left] = s[4] - s[1] = 18 - 3 = 15
✓
Query 2: sumRange(0, 4)
- Find sum of all elements
- We need:
nums[0] + nums[1] + nums[2] + nums[3] + nums[4] = 3 + 5 + 2 + 8 + 4 = 22
- Using our formula:
s[5] - s[0] = 22 - 0 = 22
✓
Query 3: sumRange(2, 2)
- Find sum of single element at index 2
- We need:
nums[2] = 2
- Using our formula:
s[3] - s[2] = 10 - 8 = 2
✓
Why the formula works:
When we calculate s[right + 1] - s[left]
:
s[right + 1]
contains the sum from index 0 toright
s[left]
contains the sum from index 0 toleft - 1
- Subtracting removes all elements before index
left
, leaving exactly the range we want
This preprocessing allows us to answer any range sum query in constant O(1) time, regardless of the range size!
Solution Implementation
1from typing import List
2from itertools import accumulate
3
4
5class NumArray:
6 def __init__(self, nums: List[int]):
7 # Create a prefix sum array with an initial 0 at the beginning
8 # prefix_sums[i] represents the sum of elements from index 0 to i-1
9 # This allows us to calculate range sums efficiently
10 self.prefix_sums = list(accumulate(nums, initial=0))
11
12 def sumRange(self, left: int, right: int) -> int:
13 # Calculate the sum of elements from index left to right (inclusive)
14 # Sum[left, right] = prefix_sums[right+1] - prefix_sums[left]
15 # This works because:
16 # - prefix_sums[right+1] contains sum of elements from 0 to right
17 # - prefix_sums[left] contains sum of elements from 0 to left-1
18 # - Subtracting gives us the sum from left to right
19 return self.prefix_sums[right + 1] - self.prefix_sums[left]
20
21
22# Your NumArray object will be instantiated and called as such:
23# obj = NumArray(nums)
24# param_1 = obj.sumRange(left,right)
25
1class NumArray {
2 // Prefix sum array where prefixSum[i] stores sum of elements from index 0 to i-1
3 private int[] prefixSum;
4
5 /**
6 * Constructor initializes the prefix sum array
7 * @param nums Input array of integers
8 */
9 public NumArray(int[] nums) {
10 int length = nums.length;
11
12 // Create prefix sum array with size n+1 for easier calculation
13 // prefixSum[0] = 0, prefixSum[i] = sum of nums[0] to nums[i-1]
14 prefixSum = new int[length + 1];
15
16 // Build prefix sum array
17 for (int i = 0; i < length; i++) {
18 prefixSum[i + 1] = prefixSum[i] + nums[i];
19 }
20 }
21
22 /**
23 * Returns the sum of elements between indices left and right (inclusive)
24 * @param left Starting index (inclusive)
25 * @param right Ending index (inclusive)
26 * @return Sum of elements from nums[left] to nums[right]
27 */
28 public int sumRange(int left, int right) {
29 // Sum from left to right = prefixSum[right+1] - prefixSum[left]
30 // This works because prefixSum[right+1] contains sum up to index right
31 // and prefixSum[left] contains sum up to index left-1
32 return prefixSum[right + 1] - prefixSum[left];
33 }
34}
35
36/**
37 * Your NumArray object will be instantiated and called as such:
38 * NumArray obj = new NumArray(nums);
39 * int param_1 = obj.sumRange(left,right);
40 */
41
1class NumArray {
2public:
3 /**
4 * Constructor: Preprocesses the input array to build a prefix sum array
5 * @param nums: The input array of integers
6 * Time Complexity: O(n), where n is the size of nums
7 * Space Complexity: O(n) for storing the prefix sum array
8 */
9 NumArray(vector<int>& nums) {
10 int n = nums.size();
11 // Create prefix sum array with size n+1
12 // prefixSum[i] stores the sum of elements from nums[0] to nums[i-1]
13 prefixSum.resize(n + 1);
14
15 // Build the prefix sum array
16 // prefixSum[0] = 0 (sum of zero elements)
17 for (int i = 0; i < n; ++i) {
18 prefixSum[i + 1] = prefixSum[i] + nums[i];
19 }
20 }
21
22 /**
23 * Calculates the sum of elements between indices left and right (inclusive)
24 * @param left: The starting index (inclusive)
25 * @param right: The ending index (inclusive)
26 * @return: The sum of elements from nums[left] to nums[right]
27 * Time Complexity: O(1)
28 */
29 int sumRange(int left, int right) {
30 // Sum from left to right = prefixSum[right+1] - prefixSum[left]
31 // This gives us the sum of elements from index left to right (inclusive)
32 return prefixSum[right + 1] - prefixSum[left];
33 }
34
35private:
36 vector<int> prefixSum; // Stores cumulative sum where prefixSum[i] = sum of nums[0...i-1]
37};
38
39/**
40 * Your NumArray object will be instantiated and called as such:
41 * NumArray* obj = new NumArray(nums);
42 * int param_1 = obj->sumRange(left,right);
43 */
44
1// Global array to store prefix sums
2let prefixSums: number[];
3
4/**
5 * Initializes the data structure with the given array of numbers.
6 * Builds a prefix sum array where prefixSums[i] represents the sum of elements from index 0 to i-1.
7 * @param nums - The input array of numbers
8 */
9function NumArray(nums: number[]): void {
10 const length = nums.length;
11
12 // Initialize prefix sum array with size n+1, all elements set to 0
13 // prefixSums[0] = 0 (sum of no elements)
14 // prefixSums[i] = sum of nums[0] to nums[i-1]
15 prefixSums = Array(length + 1).fill(0);
16
17 // Build the prefix sum array
18 // Each position stores the cumulative sum up to that point
19 for (let i = 0; i < length; i++) {
20 prefixSums[i + 1] = prefixSums[i] + nums[i];
21 }
22}
23
24/**
25 * Calculates the sum of elements between indices left and right (inclusive).
26 * Uses the prefix sum array to compute the range sum in O(1) time.
27 * @param left - The starting index of the range (inclusive)
28 * @param right - The ending index of the range (inclusive)
29 * @returns The sum of elements from nums[left] to nums[right]
30 */
31function sumRange(left: number, right: number): number {
32 // Range sum = (sum from 0 to right) - (sum from 0 to left-1)
33 // This gives us the sum from left to right inclusive
34 return prefixSums[right + 1] - prefixSums[left];
35}
36
Time and Space Complexity
Time Complexity:
- Initialization (
__init__
):O(n)
, wheren
is the length of the input arraynums
. Theaccumulate
function iterates through all elements once to build the prefix sum array. - Query (
sumRange
):O(1)
. Each range sum query only requires two array lookups and one subtraction operation, all of which are constant time operations.
Space Complexity:
O(n)
, wheren
is the length of the input array. The prefix sum arrayself.s
storesn + 1
elements (including the initial 0), which requires linear extra space.
The algorithm uses the prefix sum technique where self.s[i]
stores the sum of all elements from index 0
to index i-1
in the original array. This allows for efficient range sum queries using the formula: sum(nums[left:right+1]) = self.s[right + 1] - self.s[left]
.
Common Pitfalls
1. Off-by-One Errors with Index Mapping
The most common mistake is incorrectly mapping between the original array indices and the prefix sum array indices. Since the prefix sum array has an extra element at the beginning (starting with 0), developers often confuse the index relationships.
Incorrect Implementation:
# Wrong: Using wrong indices
def sumRange(self, left: int, right: int) -> int:
return self.prefix_sums[right] - self.prefix_sums[left - 1] # Fails when left = 0
Why it fails: When left = 0
, this would try to access prefix_sums[-1]
, which either gives an incorrect value (last element in Python) or throws an index error in other languages.
Correct Solution:
def sumRange(self, left: int, right: int) -> int:
return self.prefix_sums[right + 1] - self.prefix_sums[left]
2. Building Prefix Sum Array Without Initial Zero
Another pitfall is constructing the prefix sum array without the initial zero element, making the range sum calculation more complex and error-prone.
Incorrect Implementation:
def __init__(self, nums: List[int]):
# Wrong: No initial 0
self.prefix_sums = list(accumulate(nums)) # [1, 4, 9, 16, 25] instead of [0, 1, 4, 9, 16, 25]
Why it fails: Without the initial zero, calculating sums that start from index 0 becomes complicated, requiring special case handling.
Correct Solution:
def __init__(self, nums: List[int]):
self.prefix_sums = list(accumulate(nums, initial=0)) # Includes initial 0
3. Manual Prefix Sum Construction Errors
When implementing prefix sums manually without using accumulate
, developers sometimes make mistakes in the loop logic.
Incorrect Implementation:
def __init__(self, nums: List[int]):
self.prefix_sums = [0] * len(nums)
for i in range(len(nums)):
self.prefix_sums[i] = self.prefix_sums[i-1] + nums[i] # Wrong size and logic
Correct Manual Implementation:
def __init__(self, nums: List[int]):
self.prefix_sums = [0] * (len(nums) + 1) # Note: size is n+1
for i in range(len(nums)):
self.prefix_sums[i + 1] = self.prefix_sums[i] + nums[i]
4. Not Handling Edge Cases
While the provided solution handles edge cases well, developers might forget to consider:
- Empty arrays (though usually not tested in this problem)
- Single element arrays
- Queries where
left == right
The beauty of the prefix sum approach with initial zero is that it naturally handles all these cases without special logic.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!