Prefix Sum: 7 Practice Problems from Easy to Hard
Welcome to the prefix sum practice set. If the mechanics feel shaky, the code templates have the range-query class, the counting variant, and the difference array in all six languages. Each problem below has a hint, a visual walkthrough, and the complete solution.
Recommended Study Order
The problems climb in a deliberate order. Running Sum of 1d Array teaches the build pass with nothing else going on, so you see the running total in its purest form. Find Pivot Index adds the total-minus-left trick that powers most one-pass prefix solutions. Range Sum Query - Immutable is the class version you will write on a whiteboard, and Subarray Sum Equals K layers the hashmap on top, which is where most of the difficulty in this family lives. Range Sum Query 2D - Immutable extends the idea to grids, Corporate Flight Bookings shows the difference-array mirror, and Number of Submatrices That Sum to Target forces you to combine the 2D and hashmap techniques under one roof. Skip around only if you are already comfortable with the earlier ones; the later problems assume them.
Easy Problems
1. Running Sum of 1d Array
LeetCode 1480 | Difficulty: Easy
- Brief: return an array where each element is the sum of all the original elements up to that index.
- Why this pattern: this problem is the definition of the prefix sum. The running total is exactly the prefix array, and nothing else is asked of you.
- Hint: you can overwrite the input array. When you reach index
i, the value you need is already sitting innums[i - 1].
Visual:
graph LR
A["1, 2, 3, 4"] --> B["1, 3, 6, 10"]
B --> C["nums[1] += nums[0]"]
C --> D["nums[2] += nums[1]"]
D --> E["nums[3] += nums[2]"]
E --> F["Done"]
Code:
var runningSum = function(nums) {
for (let i = 1; i < nums.length; i++) {
nums[i] += nums[i - 1];
}
return nums;
};The loop starts at index 1 because index 0 is its own running sum. Each step reads the accumulated total from the previous slot and adds the current element, so the array turns into its own prefix array in place. The complexity is
2. Find Pivot Index
LeetCode 724 | Difficulty: Easy
- Brief: find the index where the sum of the elements to the left equals the sum of the elements to the right.
- Why this pattern: the left sum is a prefix sum by definition. With the total in hand, you never need to compute the right sum separately.
- Hint: keep a running left sum. The right sum is always
total - leftSum - nums[i].
Visual:
graph TD
A["nums = 1, 7, 3, 6, 5, 6, total = 28"] --> B["i = 0, left = 0, right = 27"]
B --> C["i = 1, left = 1, right = 20"]
C --> D["i = 2, left = 8, right = 17"]
D --> E["i = 3, left = 11, right = 11"]
E --> F["Return 3"]
Code:
var pivotIndex = function(nums) {
const total = nums.reduce((a, b) => a + b, 0);
let leftSum = 0;
for (let i = 0; i < nums.length; i++) {
if (leftSum === total - leftSum - nums[i]) {
return i;
}
leftSum += nums[i];
}
return -1;
};The trick is the one-pass form of the prefix idea. Instead of building the whole prefix array, you carry the left sum forward and derive the right sum from the total. When the two match, that index is the answer. The check uses nums[i] explicitly so the pivot element itself is excluded from both sides. This runs in
Medium Problems
3. Range Sum Query - Immutable
LeetCode 303 | Difficulty: Medium
- Brief: implement a class that answers many
sumRange(left, right)queries on a fixed array. - Why this pattern: this is the textbook use case. One build, unlimited queries, each costing.O(1)
- Hint: store the prefix array so
sumRange(left, right)is exactly one subtraction with no loop.
Visual:
graph TD
A["nums = -2, 0, 3, -5, 2, -1"] --> B["prefix = 0, -2, -2, 1, -4, -2, -3"]
B --> C["sumRange(0, 2) = prefix[3] - prefix[0]"]
C --> D["= 1 - 0 = 1"]
B --> E["sumRange(2, 5) = prefix[6] - prefix[2]"]
E --> F["= -3 - (-2) = -1"]
Code:
var NumArray = function(nums) {
this.prefix = new Array(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++) {
this.prefix[i + 1] = this.prefix[i] + nums[i];
}
};
NumArray.prototype.sumRange = function(left, right) {
return this.prefix[right + 1] - this.prefix[left];
};Notice the constructor is the entire algorithm. The prefix array is one element longer than the input, and prefix[i] stores the sum of the first i elements. The query then reads two slots and subtracts. There is nothing to loop over, which is why thousands of queries cost the same as one. Note the C++ and Go versions use 64-bit integers internally because cumulative sums can overflow a 32-bit int on large inputs.
4. Subarray Sum Equals K
LeetCode 560 | Difficulty: Medium
- Brief: count the number of contiguous subarrays whose sum equals
k. - Why this pattern: any subarray sum is a difference of two prefix sums. Counting how many pairs differ by
kis a hashmap lookup per prefix, not a search. - Hint: seed the hashmap with
{0: 1}before the loop. Subarrays that start at index 0 need that empty prefix to be found.
Visual:
graph TD
A["nums = 1, 2, 3, k = 3"] --> B["prefix 1, seen {0:1}"]
B --> C["prefix 3, need 0, seen {0:1, 1:1}"]
C --> D["found 1, count 1, seen {0:1, 1:1, 3:1}"]
D --> E["prefix 6, need 3, seen {0:1, 1:1, 3:1}"]
E --> F["found 1, count 2, answer 2"]
Code:
var subarraySum = function(nums, k) {
const seen = new Map();
seen.set(0, 1);
let sum = 0;
let count = 0;
for (const num of nums) {
sum += num;
// An earlier prefix of (sum - k) means the subarray
// between that prefix and here sums to exactly k.
count += seen.get(sum - k) || 0;
seen.set(sum, (seen.get(sum) || 0) + 1);
}
return count;
};The map stores how many times each prefix sum has appeared so far. When the running total reaches T, any earlier prefix equal to T - k marks the start of a valid subarray, so you add its count. The {0: 1} seed is the empty prefix; without it, the subarray starting at index 0 is invisible. This solves in
5. Range Sum Query 2D - Immutable
LeetCode 304 | Difficulty: Medium
- Brief: answer many submatrix sum queries on a fixed matrix.
- Why this pattern: the 1D prefix array extends to two dimensions, and a submatrix sum becomes four corner reads instead of a double loop.
- Hint: the build uses inclusion-exclusion too, subtracting the overlapping corner once.
Visual:
graph TD
A["prefix[2][3]"] --> B["whole area (0,0) to (1,2)"]
B --> C["- prefix[0][3] strip above"]
C --> D["- prefix[2][0] strip left"]
D --> E["+ prefix[0][0] corner added back"]
E --> F["= sum of (1,1) to (1,2)"]
Code:
var NumMatrix = function(matrix) {
const h = matrix.length, w = matrix[0].length;
this.prefix = Array.from({ length: h + 1 }, () => new Array(w + 1).fill(0));
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
this.prefix[i + 1][j + 1] = matrix[i][j]
+ this.prefix[i][j + 1]
+ this.prefix[i + 1][j]
- this.prefix[i][j];
}
}
};
NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
return this.prefix[row2 + 1][col2 + 1]
- this.prefix[row1][col2 + 1]
- this.prefix[row2 + 1][col1]
+ this.prefix[row1][col1];
};The build is the 2D version of the 1D loop. Each cell combines the rectangle above, the rectangle to the left, and the current matrix value, then subtracts the corner that was counted in both rectangles. The query reverses that process: it takes the big rectangle and peels away the top strip, the left strip, and then re-adds the corner that was removed twice. Build cost is
6. Corporate Flight Bookings
LeetCode 1109 | Difficulty: Medium
- Brief: apply many
[first, last, seats]booking increments to a range of flights and return the final seat counts. - Why this pattern: this is the difference array, the update-side mirror of the prefix sum. One
O(1)operation per booking replaces a loop over the whole range. - Hint: increment at the start, decrement just after the end, then one prefix pass over the result array. Watch the 1-indexed flight numbering.
Visual:
graph TD
A["bookings: [1,2,10], [2,3,20], [2,5,25], n = 5"] --> B["diff: +10 at 0, -10 at 2"]
B --> C["+20 at 1, -20 at 3"]
C --> D["+25 at 1, -25 at 5 (discarded)"]
D --> E["diff = 10, 45, -10, -20, 0"]
E --> F["prefix pass = 10, 55, 45, 25, 25"]
Code:
var corpFlightBookings = function(bookings, n) {
const diff = new Array(n).fill(0);
for (const [first, last, seats] of bookings) {
diff[first - 1] += seats;
if (last < n) {
diff[last] -= seats;
}
}
const result = new Array(n);
let run = 0;
for (let i = 0; i < n; i++) {
run += diff[i];
result[i] = run;
}
return result;
};Flights are 1-indexed, so a booking starting at flight first lands at diff index first - 1. The +seats marks where the increment begins, and the -seats at last (the index after the last affected flight) marks where it ends. The final prefix pass over diff carries the running total forward, and every flight that was inside a booking range picks up the right count. This is
Hard Problems
7. Number of Submatrices That Sum to Target
LeetCode 1074 | Difficulty: Hard
- Brief: count how many submatrices in a matrix sum to a target value.
- Why this pattern: compress each pair of rows into a 1D array of column sums, then apply the hashmap counting from Subarray Sum Equals K down the columns.
- Hint: fix the top row, extend the bottom row downward, and maintain a running column-sum array. For each such pair, the problem reduces to a 1D counting problem.
Visual:
graph TD
A["Fix top row"] --> B["Extend bottom row"]
B --> C["colSums = sum of rows top..bottom per column"]
C --> D["Count subarrays of colSums equal to target"]
D --> E["Repeat for every (top, bottom) pair"]
Code:
var numSubmatrixSumTarget = function(matrix, target) {
const m = matrix.length, n = matrix[0].length;
let count = 0;
for (let top = 0; top < m; top++) {
const colSums = new Array(n).fill(0);
for (let bottom = top; bottom < m; bottom++) {
for (let c = 0; c < n; c++) {
colSums[c] += matrix[bottom][c];
}
// Count subarrays of colSums that sum to target.
const seen = new Map();
seen.set(0, 1);
let run = 0;
for (let c = 0; c < n; c++) {
run += colSums[c];
count += seen.get(run - target) || 0;
seen.set(run, (seen.get(run) || 0) + 1);
}
}
}
return count;
};This problem is where the pattern earns its keep. Each submatrix is defined by a top row, a bottom row, and a contiguous range of columns. Fixing the top and bottom rows collapses the band into a 1D array of column sums, and then the exact hashmap trick from Subarray Sum Equals K counts every valid column range. Repeating for all
These seven problems cover the whole prefix sum family. Start with the build pass in Running Sum, learn to compute the right side from the total in Find Pivot Index, then layer on the class, the hashmap, the 2D grid, the difference array, and finally the combined challenge. By the end you should be able to look at a problem and tell within seconds whether it wants a prefix array, a hashmap of prefix sums, or a difference array. The code templates are worth revisiting once you have seen where each one actually shows up.