Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 7

Prefix Sums for O(1) Range Sum Queries

Recommended resources

Welcome to the next lesson in our "Complexity Analysis and Core Patterns" module. In our previous session, we mastered the sliding window technique, an excellent approach for finding an optimal contiguous subarray in linear time. We saw how to efficiently find the single subarray with the maximum sum by sliding a window and updating the sum in time.

But what if the problem changes? Instead of finding one optimal subarray, imagine you need to answer many queries about the sum of different subarrays. For example, "What is the sum from index 2 to 10?", followed by "What is the sum from index 5 to 15?", and so on. Running the sliding window algorithm for each query would be inefficient.

Today, we will learn a technique specifically designed for this scenario: prefix sums. Your goal is to understand and implement this pattern, which allows you to answer any number of range sum queries in constant () time after a single, linear-time () preprocessing step. This is a fundamental technique frequently tested in SDE interviews.

The Problem: Repeated Range Sum Queries

Let's consider a common problem: you are given a static array of numbers, and you need to build a service that can quickly answer queries for the sum of elements between any two indices, left and right.

The most straightforward approach is to loop from left to right and add up the elements for each query.

// Naive approach
public int sumRange(int[] nums, int left, int right) {
    int sum = 0;
    for (int i = left; i <= right; i++) {
        sum += nums[i];
    }
    return sum;
}

If the range has elements, this takes time. In the worst case, a query could span the entire array, making it an operation. If you have queries, the total time complexity would be , which is too slow for many applications.

The key to optimization lies in precomputation. Can we invest some time upfront to make all future queries faster?


The Prefix Sum Solution

The prefix sum technique does exactly this. The core idea is to create an auxiliary array, let's call it prefixSum, where each element prefixSum[i] stores the cumulative sum of the original array's elements from the beginning up to index i.

The following video provides an excellent conceptual introduction to this idea. It starts with the naive approach and builds the intuition for why precomputing cumulative sums is so powerful.

Prefix Sum Array and Range Sum Queries

Watch the video from the beginning to understand the problem setup and the naive solution. Then, focus on how the prefix sum array is constructed and used. The problem: This section introduces the scenario of handling multiple range queries and the slowness of the naive approach. Constructing the array: This part explains how to build the prefix sum array (called p in the video) with a single pass. The query formula: This is the crucial part that reveals how to get any range sum in constant time using the precomputed array.

As the video explained, once you have the prefix sum array, you can find the sum of any range [left, right] with a single subtraction.

Let's formalize this. Given an original array nums, we will construct a prefixSum array. A very common and robust implementation detail is to make the prefixSum array one element larger than the original nums array. This helps simplify the calculations and avoid edge cases.

We define it as follows:

  • prefixSum[0] = 0
  • prefixSum[i] = nums[0] + nums[1] + ... + nums[i-1] for i > 0

In other words, prefixSum[i] is the sum of the first i elements of nums.
This array can be built in time using the recurrence relation:

With this structure, the sum of elements from index left to right (inclusive) is simply:

Why does this work?

  • prefixSum[right + 1] gives you the sum from nums[0] up to nums[right].
  • prefixSum[left] gives you the sum from nums[0] up to nums[left - 1].

Subtracting the two cancels out the initial part of the sum, leaving exactly the sum of nums[left] through nums[right]. This entire process is captured perfectly in the following diagram.

This diagram shows the complete workflow: starting with an original array, performing the O(n) precomputation to build the prefix sum array, and then using the formula to answer a range query in O(1) time.

From Theory to Java Implementation

Now, let's translate this into a practical Java class, as you might be asked to do in an interview (this is the essence of LeetCode problem 303: Range Sum Query - Immutable).

The following resource provides an in-depth explanation and a clean Java implementation. Pay close attention to how the prefixSum array is one larger than the input array and how this simplifies the sumRange calculation.

303. Range Sum Query - Immutable - In-Depth Explanation

This article from AlgoMonster breaks down the problem and explains the prefix sum approach with clear examples. It's an excellent resource for understanding the implementation details required in a coding interview.

Read the sections titled "Intuition" and "Solution Approach." Focus on the time complexity trade-off and how the data structure is defined. The example walkthrough will solidify your understanding of the formula s[right + 1] - s[left].

With the logic clear, let's examine the corresponding Java code.

303. Range Sum Query - Immutable - In-Depth Explanation

This section provides the complete Java code for the NumArray class.

Find the Java code block. Analyze the constructor, public NumArray(int[] nums), to see the O(n) preprocessing step. Then, look at the sumRange method to see the O(1) query implementation. Notice there are no if statements needed to handle edge cases like left = 0, because the n+1 size array with a leading zero handles it automatically.


Complexity and Common Pitfalls

For your Google interview preparation, analyzing complexity and anticipating edge cases is crucial. Let's summarize and then look at potential mistakes.

Time and Space Complexity:

  • Preprocessing (Constructor): The constructor iterates through the input array once to build the prefixSum array. This takes time.
  • Query (sumRange): The sumRange method performs a single subtraction, regardless of the range size. This takes time.
  • Space Complexity: We store an additional array of size . This requires space.

The key trade-off, which you should be prepared to discuss, is using extra space to reduce query time from to . This is highly advantageous when you expect many queries on an unchanging array.

Making this technique robust means avoiding common mistakes. The most frequent are off-by-one errors in indexing.

303. Range Sum Query - Immutable - In-Depth Explanation

This part of the article explicitly discusses common mistakes developers make when implementing prefix sums.

Read the section "Common Pitfalls". Pay special attention to the first point, "Off-by-One Errors", and how the prefix_sums[right + 1] - prefix_sums[left] formula elegantly avoids issues when left is 0. This is a mark of a clean, well-thought-out implementation.


Conclusion

In this lesson, you've learned the prefix sum technique, a powerful pattern for handling range sum queries on static arrays. It embodies a classic algorithmic trade-off: investing in preprocessing to achieve remarkable query performance.

Here are the key takeaways:

  • Prefix Sum: A technique that precomputes cumulative sums to answer range queries efficiently.
  • Preprocessing: Building the prefix sum array takes time and uses space.
  • Querying: Finding the sum of any range [left, right] takes time with a simple subtraction: prefixSum[right + 1] - prefixSum[left].
  • Implementation: Using a prefixSum array of size n+1 with an initial 0 is a robust way to avoid edge-case handling for ranges starting at index 0.

We've just seen a technique that cleverly uses extra space to gain a significant advantage in time. In our next lesson, we'll tackle problems with the opposite constraint. We will explore in-place array manipulation, where the challenge is to modify an array using no extra space, forcing you to be creative within the array's own memory.