Pattern family: Array / Subarray / Range Query Notes & Templates: 01_prefix_sum_notes.md Language: Java
LeetCode #303 | Difficulty: Easy | Company: Amazon, Google | Category: Basic Prefix Array
Given an integer array, handle multiple queries each asking for the sum of elements between indices
leftandrightinclusive.
Precompute prefix array once in O(n). Every query answered in O(1) using prefix[right+1] - prefix[left].
| Step | Action | Note |
|---|---|---|
| Build | prefix[i+1] = prefix[i] + nums[i] |
size n+1, prefix[0]=0 |
| Query | prefix[right+1] - prefix[left] |
O(1) per query |
class NumArray {
private int[] prefix;
public NumArray(int[] nums) {
prefix = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++)
prefix[i + 1] = prefix[i] + nums[i];
}
public int sumRange(int left, int right) {
return prefix[right + 1] - prefix[left];
}
}nums = [β2, 0, 3, β5, 2, β1]
prefix = [0, β2, β2, 1, β4, β2, β3]
sumRange(0, 2) = prefix[3] β prefix[0] = 1 β 0 = 1 β (β2+0+3)
sumRange(2, 5) = prefix[6] β prefix[2] = β3 β (β2) = β1 β (3β5+2β1)
LeetCode #724 | Difficulty: Easy | Company: Amazon, Facebook | Category: Basic Prefix Array
Find the leftmost index where the sum of all elements to the left equals the sum of all elements to the right.
leftSum == total - leftSum - nums[i] β 2 * leftSum + nums[i] == total. No HashMap needed β single pass after computing total.
| Step | Action | Note |
|---|---|---|
| 1 | Compute total = sum of all elements |
one pass |
| 2 | Walk leftβright, track leftSum |
starts at 0 |
| 3 | Check 2 * leftSum + nums[i] == total |
pivot condition |
| 4 | Add nums[i] to leftSum |
update after check |
class Solution {
public int pivotIndex(int[] nums) {
int leftSum = 0;
int rightSum = 0;
int sum = 0;
for (int num : nums) {
sum += num;
}
for (int i = 0; i < nums.length; i++) {
rightSum = sum - leftSum - nums[i];
if (leftSum == rightSum) {
return i;
}
leftSum += nums[i];
}
return -1;
}
}Note: Explicitly computes
rightSum = total - leftSum - nums[i]and compares directly β easier to read. Reference uses the equivalent2*leftSum + nums[i] == totalto avoid the extra variable.
public int pivotIndex(int[] nums) {
int total = 0;
for (int n : nums) total += n;
int leftSum = 0;
for (int i = 0; i < nums.length; i++) {
if (2 * leftSum + nums[i] == total) return i;
leftSum += nums[i];
}
return -1;
}nums = [1, 7, 3, 6, 5, 6]
total = 28
i=0: 2*0 + 1 = 1 β 28, leftSum=1
i=1: 2*1 + 7 = 9 β 28, leftSum=8
i=2: 2*8 + 3 = 19 β 28, leftSum=11
i=3: 2*11 + 6 = 28 == 28 β return 3 β
(left sum = 1+7+3=11, right sum = 5+6=11)
LeetCode #560 | Difficulty: Medium | Company: Amazon, Facebook, Google | Category: HashMap Count
Count the total number of subarrays whose sum equals
k.
sum(i,j) = k β prefix[j] - prefix[i] = k β prefix[i] = prefix[j] - k.
For every j, count how many previous prefix sums equal currentSum - k.
| Step | Action | Note |
|---|---|---|
| Init | map = {0: 1}, sum = 0, count = 0 |
seed map before loop |
| Each num | sum += num |
running prefix sum |
| Lookup | count += map.get(sum - k, 0) |
subarrays ending here with sum=k |
| Store | map[sum]++ |
after lookup, not before |
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> hm = new HashMap<>();
hm.put(0, 1);
int sum = 0;
int res = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
int need = sum - k;
res += hm.getOrDefault(need, 0);
hm.put(sum, hm.getOrDefault(sum, 0) + 1);
}
return res;
}
}Note: Uses
need = sum - kas an explicit variable β makes the lookup intent very clear. Reference usesmap.merge()which is more concise for incrementing. Both are identical in logic.
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // empty prefix β sum 0 seen once
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
count += map.getOrDefault(sum - k, 0);
map.merge(sum, 1, Integer::sum);
}
return count;
}nums = [1, 2, 3], k = 3
map={0:1}, sum=0, count=0
num=1: sum=1, look for 1-3=-2 β 0, map={0:1, 1:1}
num=2: sum=3, look for 3-3=0 β 1, map={0:1, 1:1, 3:1}, count=1
num=3: sum=6, look for 6-3=3 β 1, map={0:1,1:1,3:1,6:1}, count=2
Output: 2 (subarrays [1,2] and [3]) β
LeetCode #525 | Difficulty: Medium | Company: Amazon, Facebook | Category: HashMap Remap
Find the maximum length subarray with equal number of 0s and 1s.
Remap 0 β -1. Now "equal 0s and 1s" = "subarray sum = 0". Store the first index where each prefix sum appears. When the same sum reappears at index j, subarray (firstIndex, j] has sum 0 β equal 0s and 1s.
| Step | Action | Note |
|---|---|---|
| Init | map = {0: -1}, sum = 0, maxLen = 0 |
seed with index -1 |
| Remap | sum += nums[i] == 0 ? -1 : 1 |
0 becomes -1 |
| If seen | maxLen = max(maxLen, i - map[sum]) |
length of subarray |
| If new | map[sum] = i |
store first occurrence only |
class Solution {
public int findMaxLength(int[] nums) {
HashMap<Integer, Integer> hm = new HashMap<>();
int zero = 0;
int one = 0;
int diff = 0;
int ans = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) {
zero++;
} else {
one++;
}
diff = zero - one;
if (diff == 0) {
ans = Math.max(ans, i + 1);
continue;
}
if (hm.containsKey(diff)) {
ans = Math.max(ans, i - hm.get(diff));
} else {
hm.put(diff, i);
}
}
return ans;
}
}Note: Tracks
zeroandonecounters separately and usesdiff = zero - oneas the key β very intuitive to read. Reference remaps0β-1directly in the running sum which achieves the same thing more concisely. Also, yourdiff==0early check handles the case where the entire prefix from 0 toiis balanced β reference handles this via themap.put(0, -1)seed (same effect, different approach).
public int findMaxLength(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1); // prefix sum 0 seen at index -1 (before array)
int sum = 0, maxLen = 0;
for (int i = 0; i < nums.length; i++) {
sum += (nums[i] == 0) ? -1 : 1;
if (map.containsKey(sum)) {
maxLen = Math.max(maxLen, i - map.get(sum));
} else {
map.put(sum, i); // first occurrence only β maximises length
}
}
return maxLen;
}nums = [0, 1, 0, 1, 1]
remapped: [β1, 1, β1, 1, 1]
i=0: sum=β1, new β map={0:β1, β1:0}
i=1: sum= 0, seen at β1 β len=1β(β1)=2, maxLen=2
i=2: sum=β1, seen at 0 β len=2β0=2, maxLen=2
i=3: sum= 0, seen at β1 β len=3β(β1)=4, maxLen=4
i=4: sum= 1, new β map={..., 1:4}
Output: 4 (subarray [0,1,0,1]) β
LeetCode #325 | Difficulty: Medium | Company: Amazon | Category: HashMap First-seen
Find the length of the longest subarray with sum equal to
k. (Array may contain negatives.)
Same as Subarray Sum Equals K but store first occurrence of each prefix sum (for maximum length). When sum - k is found in map, update maxLen.
| Step | Action | Note |
|---|---|---|
| Init | map = {0: -1}, sum = 0, maxLen = 0 |
seed index -1 |
| Each num | sum += num |
running prefix |
If sum-k in map |
maxLen = max(maxLen, i - map[sum-k]) |
update length |
| Store sum | only if not already in map | first occurrence = max length |
public int maxSubArrayLen(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int sum = 0, maxLen = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (map.containsKey(sum - k)) {
maxLen = Math.max(maxLen, i - map.get(sum - k));
}
if (!map.containsKey(sum)) {
map.put(sum, i); // store first occurrence only
}
}
return maxLen;
}nums = [1, β1, 5, β2, 3], k = 3
prefix sums: [0, 1, 0, 5, 3, 6]
i=0: sum=1, look for 1-3=β2 β not found, map={0:β1, 1:0}
i=1: sum=0, look for 0-3=β3 β not found, sum 0 already in map β skip
i=2: sum=5, look for 5-3=2 β not found, map={..., 5:2}
i=3: sum=3, look for 3-3=0 β found at β1, len=3β(β1)=4, maxLen=4
i=4: sum=6, look for 6-3=3 β found at 3, len=4β3=1, maxLen=4
Output: 4 (subarray [1,β1,5,β2]) β
LeetCode #974 | Difficulty: Medium | Company: Amazon, Google | Category: Modulo HashMap
Return the number of subarrays whose sum is divisible by
k.
sum(i,j) % k == 0 β (prefix[j] - prefix[i]) % k == 0 β prefix[j] % k == prefix[i] % k. Count pairs with equal remainders. Normalise negative remainders with ((rem % k) + k) % k.
| Step | Action | Note |
|---|---|---|
| Init | map = {0: 1}, sum = 0, count = 0 |
seed remainder 0 |
| Each num | sum += num |
running prefix |
| Remainder | rem = ((sum % k) + k) % k |
normalise negatives |
| Lookup | count += map.get(rem, 0) |
pairs with same remainder |
| Store | map[rem]++ |
after lookup |
class Solution {
public int subarraysDivByK(int[] nums, int k) {
int ans = 0;
HashMap<Integer, Integer> hm = new HashMap<>();
hm.put(0, 1);
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
int rem = sum % k;
if (rem < 0) {
rem += k;
}
ans += hm.getOrDefault(rem, 0);
hm.put(rem, hm.getOrDefault(rem, 0) + 1);
}
return ans;
}
}Note: Uses
if (rem < 0) rem += kto handle negatives β explicit and easy to understand. Reference uses the one-liner((sum % k) + k) % kwhich handles it in one shot. Both produce the same result.
public int subarraysDivByK(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
int rem = ((sum % k) + k) % k; // normalise negative remainder
count += map.getOrDefault(rem, 0);
map.merge(rem, 1, Integer::sum);
}
return count;
}nums = [4, 5, 0, β2, β3, 1], k = 5
prefix sums: [0, 4, 9, 9, 7, 4, 5]
remainders: [0, 4, 4, 4, 2, 4, 0]
rem=0: map={0:1} β count+=1=1, map={0:2}
rem=4: new β map={0:2,4:1}
rem=4: found 1 β count=2, map={0:2,4:2}
rem=4: found 2 β count=4, map={0:2,4:3}
rem=2: new β map={...,2:1}
rem=4: found 3 β count=7, map={0:2,4:4,2:1}
rem=0: found 2 β count=9 map={0:3,...}
Output: 7 β
LeetCode #523 | Difficulty: Medium | Company: Amazon, Facebook | Category: Modulo HashMap
Return
trueif there exists a subarray of length at least 2 whose sum is a multiple ofk.
Same modulo trick. Store first index where each remainder appears. If same remainder seen again at index j and j - firstIndex >= 2 β valid subarray exists.
| Step | Action | Note |
|---|---|---|
| Init | map = {0: -1}, sum = 0 |
seed index -1 |
| Each num | sum += num, rem = sum % k |
running prefix |
| If seen | if j - map[rem] >= 2 β return true |
length β₯ 2 check |
| If new | map[rem] = j |
store first occurrence |
public boolean checkSubarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
int rem = sum % k;
if (map.containsKey(rem)) {
if (i - map.get(rem) >= 2) return true; // length at least 2
} else {
map.put(rem, i); // first occurrence only
}
}
return false;
}nums = [23, 2, 4, 6, 7], k = 6
i=0: sum=23, rem=5, new β map={0:β1, 5:0}
i=1: sum=25, rem=1, new β map={..., 1:1}
i=2: sum=29, rem=5, seen at 0, iβ0=2 β₯ 2 β return true β
(subarray [2,4] has sum 6, divisible by 6)
LeetCode #304 | Difficulty: Medium | Company: Amazon, Google | Category: 2D Prefix Array
Handle multiple queries on a 2D matrix, each returning the sum of elements inside a rectangle.
Build a 2D prefix array using inclusion-exclusion. Each query answered in O(1).
| Step | Action | Note |
|---|---|---|
| Build | p[i][j] = mat[i-1][j-1] + p[i-1][j] + p[i][j-1] - p[i-1][j-1] |
(m+1)Γ(n+1) array |
| Query | p[r2+1][c2+1] - p[r1][c2+1] - p[r2+1][c1] + p[r1][c1] |
inclusion-exclusion |
class NumMatrix {
private int[][] prefix;
public NumMatrix(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
prefix = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
prefix[i][j] = matrix[i-1][j-1]
+ prefix[i-1][j]
+ prefix[i][j-1]
- prefix[i-1][j-1]; // add back overlap
}
public int sumRegion(int r1, int c1, int r2, int c2) {
return prefix[r2+1][c2+1]
- prefix[r1][c2+1]
- prefix[r2+1][c1]
+ prefix[r1][c1];
}
}matrix = [[3,0,1,4],
[5,6,3,2],
[1,2,0,1]]
sumRegion(1,1,2,2):
prefix[3][3] - prefix[1][3] - prefix[3][1] + prefix[1][1]
= (3+0+1+5+6+3+1+2+0) - (3+0+1) - (3+5+1) - (3)
= 21 - 4 - 9 + 3 = 11 β (6+3+2+0)
LeetCode #363 | Difficulty: Hard | Company: Google, Amazon | Category: 2D Prefix + Sorted Set
Given a matrix and integer
k, find the max sum of a rectangle in the matrix such that its sum is no larger thank.
Fix top and bottom rows. Compress each column to a 1D array of column sums. Then find the max subarray sum β€ k in the 1D array using prefix sums + a sorted TreeSet. Binary search for the smallest prefix sum β₯ currentSum - k.
| Step | Action | Note |
|---|---|---|
Fix top row r1 |
iterate 0 to m-1 | outer loop |
Fix bottom row r2 |
iterate r1 to m-1 | inner loop |
| Build colSum | colSum[j] += matrix[r2][j] |
compress rows to 1D |
| 1D problem | find max subarray sum β€ k | prefix + TreeSet |
| For each prefix | set.ceiling(sum - k) β smallest valid prev prefix |
binary search |
public int maxSumSubmatrix(int[][] matrix, int k) {
int m = matrix.length, n = matrix[0].length, res = Integer.MIN_VALUE;
for (int r1 = 0; r1 < m; r1++) {
int[] colSum = new int[n];
for (int r2 = r1; r2 < m; r2++) {
for (int j = 0; j < n; j++) colSum[j] += matrix[r2][j];
// Max subarray sum β€ k using prefix + TreeSet
TreeSet<Integer> set = new TreeSet<>();
set.add(0);
int sum = 0;
for (int col : colSum) {
sum += col;
Integer target = set.ceiling(sum - k); // smallest prefix β₯ sum-k
if (target != null) res = Math.max(res, sum - target);
set.add(sum);
}
}
}
return res;
}matrix = [[1,0,1],[0,β2,3]], k = 2
Fix r1=0, r2=1: colSum = [1, β2, 4]
prefix sums: 0, 1, β1, 3
set={0}: sum=1, ceiling(1β2=β1)=0, res=1β0=1
set={0,1}: sum=β1, ceiling(β1β2=β3)=0, res=max(1,β1β0)=1
set={0,1,β1}: sum=3, ceiling(3β2=1)=1, res=max(1,3β1)=2
Output: 2 β
LeetCode #862 | Difficulty: Hard | Company: Google, Amazon | Category: Prefix + Monotonic Deque
Return the length of the shortest non-empty subarray with sum at least
k. Array may contain negatives.
Sliding window fails with negatives. Build prefix array. Use a monotonic increasing deque of prefix indices. For each i, pop front while prefix[i] - prefix[front] >= k β these are valid subarrays, take the shortest. Maintain deque increasing by popping back when current prefix β€ back.
If prefix[i] <= prefix[j] and i < j:
prefix[j] can never be a better left endpoint than prefix[i]
(same or larger sum, but closer to current index = shorter subarray)
β discard j from the back
| Step | Action | Note |
|---|---|---|
| Build | prefix[i+1] = prefix[i] + nums[i] |
long[] to avoid overflow |
| Deque front | while prefix[i] - prefix[front] >= k β update res, pop front |
shrink from left |
| Deque back | while prefix[i] <= prefix[back] β pop back |
maintain increasing |
| Push | deque.addLast(i) |
always push current index |
public int shortestSubarray(int[] nums, int k) {
int n = nums.length;
long[] prefix = new long[n + 1];
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
int res = Integer.MAX_VALUE;
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i <= n; i++) {
// Front: valid subarray found β take shortest
while (!deque.isEmpty() && prefix[i] - prefix[deque.peekFirst()] >= k)
res = Math.min(res, i - deque.pollFirst());
// Back: maintain increasing prefix values
while (!deque.isEmpty() && prefix[i] <= prefix[deque.peekLast()])
deque.pollLast();
deque.addLast(i);
}
return res == Integer.MAX_VALUE ? -1 : res;
}nums = [2, β1, 2], k = 3
prefix = [0, 2, 1, 3]
i=0: deque=[0]
i=1: prefix[1]=2, back check: 2>0 ok β deque=[0,1]
i=2: prefix[2]=1, back check: 1<=prefix[1]=2 β pop 1, deque=[0,2]
i=3: prefix[3]=3, front: 3-prefix[0]=3>=3 β res=3-0=3, pop 0
deque=[2], front: 3-prefix[2]=2 < 3 β stop
Output: 3 ([2,β1,2]) β
LeetCode #327 | Difficulty: Hard | Company: Google | Category: Prefix + Merge Sort
Count the number of range sums
sum(i,j)that lie in[lower, upper].
Build prefix array. A range sum prefix[j] - prefix[i] is in [lower, upper] iff prefix[i] is in [prefix[j] - upper, prefix[j] - lower]. Count such pairs using merge sort on the prefix array β during merge, for each right element, find how many left elements fall in the valid range using two pointers.
| Step | Action | Note |
|---|---|---|
| Build | prefix array of size n+1 | prefix[0]=0 |
| Merge sort | sort prefix array, count valid pairs during merge | divide and conquer |
| Two pointers | for each right half element, advance lo/hi in left half | [prefix[j]-upper, prefix[j]-lower] |
public int countRangeSum(int[] nums, int lower, int upper) {
int n = nums.length;
long[] prefix = new long[n + 1];
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
return mergeCount(prefix, 0, prefix.length, lower, upper);
}
private int mergeCount(long[] prefix, int left, int right, int lower, int upper) {
if (right - left <= 1) return 0;
int mid = (left + right) / 2;
int count = mergeCount(prefix, left, mid, lower, upper)
+ mergeCount(prefix, mid, right, lower, upper);
int lo = mid, hi = mid;
for (int i = left; i < mid; i++) {
// Advance hi: prefix[hi] - prefix[i] <= upper
while (hi < right && prefix[hi] - prefix[i] <= upper) hi++;
// Advance lo: prefix[lo] - prefix[i] < lower
while (lo < right && prefix[lo] - prefix[i] < lower) lo++;
count += hi - lo;
}
// Standard merge
long[] sorted = Arrays.copyOfRange(prefix, left, right);
Arrays.sort(sorted);
System.arraycopy(sorted, 0, prefix, left, sorted.length);
return count;
}nums = [β2, 5, β1], lower = β2, upper = 2
prefix = [0, β2, 3, 2]
Valid range sums (i,j):
(0,0): β2 β
(2,3): 2β3=β1 β
(0,3): 2β0=2 β
Output: 3 β
LeetCode #152 | Difficulty: Medium | Company: Amazon, Google | Category: Prefix Product (min/max tracking)
Find the contiguous subarray with the largest product.
Unlike sum, product can flip sign. Track both maxProd and minProd at each index (min can become max after multiplying by a negative). Reset both to nums[i] when current element breaks the streak (i.e., zero resets everything).
| Step | Condition | Action |
|---|---|---|
| Each num | update both max and min | newMax = max(num, max*num, min*num) |
newMin = min(num, max*num, min*num) |
||
| Result | track global max | res = max(res, maxProd) |
public int maxProduct(int[] nums) {
int maxProd = nums[0], minProd = nums[0], res = nums[0];
for (int i = 1; i < nums.length; i++) {
int num = nums[i];
int tempMax = Math.max(num, Math.max(maxProd * num, minProd * num));
minProd = Math.min(num, Math.min(maxProd * num, minProd * num));
maxProd = tempMax;
res = Math.max(res, maxProd);
}
return res;
}nums = [2, 3, β2, 4]
i=1(3): maxProd=max(3,6,3)=6, minProd=min(3,6,3)=3, res=6
i=2(β2): maxProd=max(β2,β12,β6)=β2, minProd=min(β2,β12,β6)=β12, res=6
i=3(4): maxProd=max(4,β8,β48)=4, minProd=min(4,β8,β48)=β48, res=6
Output: 6 (subarray [2,3]) β
nums = [β2, 3, β4]
i=1(3): maxProd=3, minProd=β6
i=2(β4): maxProd=max(β4,β12,24)=24, res=24 β
| Problem | Category | Key technique |
|---|---|---|
| Range Sum Query | Basic prefix | prefix[r+1] - prefix[l] |
| Find Pivot Index | Basic prefix | 2*leftSum + nums[i] == total |
| Subarray Sum = K | HashMap count | map.put(0,1), count sum-k |
| Contiguous Array | HashMap remap | 0β-1, store first index |
| Longest Subarray Sum K | HashMap first-seen | store first index, max length |
| Subarray Sums Div by K | Modulo HashMap | ((sum%k)+k)%k, count pairs |
| Continuous Subarray Sum | Modulo first-seen | first index + length β₯ 2 check |
| Range Sum Query 2D | 2D prefix | inclusion-exclusion build + query |
| Max Rectangle β€ K | 2D prefix + TreeSet | fix rows, 1D + ceiling(sum-k) |
| Shortest Subarray β₯ K | Prefix + Deque | monotone increasing deque |
| Count Range Sum | Prefix + Merge Sort | count valid pairs during merge |
| Maximum Product Subarray | Prefix product | track both max and min product |