Prefix Sum & Suffix Techniques
A powerful technique for rapid sum, product, and other calculations in ranges.
1. The Prefix Sum Technique
A prefix sum array is a pre-computed array where each element `prefix[i]` stores the sum of all elements from the start of the original array up to index `i`. It's incredibly useful for answering range sum queries quickly.
Creating a Prefix Sum Array
Formula:
- `prefix[0] = arr[0]`
- `prefix[i] = prefix[i-1] + arr[i]` for `i > 0`
Visualization
Original Array `arr`:
Prefix Sum Array `prefix`:
public class PrefixSumCreator {
public static int[] createPrefixSum(int[] arr) {
if (arr == null || arr.length == 0) {
return new int[0];
}
int n = arr.length;
int[] prefix = new int[n];
prefix[0] = arr[0];
for (int i = 1; i < n; i++) {
prefix[i] = prefix[i - 1] + arr[i];
}
return prefix;
}
}
Dry Run: `arr = [2, 8, 3, 9, 4, 5]`
| i | arr[i] | Calculation | prefix Array State |
|---|---|---|---|
| 0 | 2 | `prefix[0] = arr[0]` | `[2, 0, 0, 0, 0, 0]` |
| 1 | 8 | `prefix[1] = prefix[0] + arr[1] = 2 + 8` | `[2, 10, 0, 0, 0, 0]` |
| 2 | 3 | `prefix[2] = prefix[1] + arr[2] = 10 + 3` | `[2, 10, 13, 0, 0, 0]` |
| 3 | 9 | `prefix[3] = prefix[2] + arr[3] = 13 + 9` | `[2, 10, 13, 22, 0, 0]` |
| 4 | 4 | `prefix[4] = prefix[3] + arr[4] = 22 + 4` | `[2, 10, 13, 22, 26, 0]` |
| 5 | 5 | `prefix[5] = prefix[4] + arr[5] = 26 + 5` | `[2, 10, 13, 22, 26, 31]` |
2. Applications & Interview Problems
Problem 1: Range Sum Query
Problem: Given an array `arr`, handle multiple queries where each query asks for the sum of elements from index `L` to `R` (inclusive).
Optimization with Prefix Sum
Formula: `sum(L, R) = prefix[R] - prefix[L-1]`. This gives an O(1) query time after an initial O(N) setup.
public class RangeSumQuery {
private int[] prefix;
public RangeSumQuery(int[] arr) {
if (arr == null || arr.length == 0) return;
prefix = new int[arr.length];
prefix[0] = arr[0];
for(int i = 1; i < arr.length; i++) {
prefix[i] = prefix[i-1] + arr[i];
}
}
public int getSum(int L, int R) {
if (L == 0) {
return prefix[R];
}
return prefix[R] - prefix[L - 1];
}
}
Problem 2: Subarray with Zero Sum
Problem: Given an array of integers, find if there is a subarray with a sum equal to 0.
Approach with Prefix Sum & HashMap
If a prefix sum `X` appears at two different indices `i` and `j`, the sum of the elements between them must be 0. We use a HashSet to track sums we've already seen.
import java.util.HashSet;
import java.util.Set;
public class ZeroSumSubarray {
public static boolean hasZeroSumSubarray(int[] arr) {
Set<Long> sums = new HashSet<>();
long currentSum = 0;
sums.add(0L);
for (int val : arr) {
currentSum += val;
if (sums.contains(currentSum)) {
return true;
}
sums.add(currentSum);
}
return false;
}
}
Dry Run: `arr = [4, 2, -3, 1, 6]`
| Index | `arr[i]` | `currentSum` | `sums` Set | Action |
|---|---|---|---|---|
| - | - | 0 | `{0}` | Initial state. |
| 0 | 4 | 4 | `{0, 4}` | Add `4`. |
| 1 | 2 | 6 | `{0, 4, 6}` | Add `6`. |
| 2 | -3 | 3 | `{0, 4, 6, 3}` | Add `3`. |
| 3 | 1 | 4 | `{0, 4, 6, 3}` | Set contains `4`. Return `true`. |
The subarray `[2, -3, 1]` sums to 0. This was detected because the prefix sum `4` (from index 0) repeated after processing index 3.
Problem 3: Find Pivot Index
Problem: Given an array of integers `nums`, find the "pivot index." This is the index where the sum of all numbers to the left of the index is equal to the sum of all numbers to the right of the index. If no such index exists, return -1.
Approach using a Running Prefix Sum
Instead of creating two separate prefix and suffix sum arrays, we can do this in one pass. First, calculate the `totalSum` of the entire array. Then, iterate through the array keeping track of a `leftSum`. For each index `i`, the `rightSum` is simply `totalSum - leftSum - nums[i]`. If `leftSum` equals `rightSum`, we've found our pivot.
public class PivotIndexFinder {
public int pivotIndex(int[] nums) {
int totalSum = 0;
for (int num : nums) {
totalSum += num;
}
int leftSum = 0;
for (int i = 0; i < nums.length; i++) {
int rightSum = totalSum - leftSum - nums[i];
if (leftSum == rightSum) {
return i; // Found the pivot index
}
leftSum += nums[i];
}
return -1; // No pivot index found
}
}
Dry Run: `nums = [1, 7, 3, 6, 5, 6]`
First, `totalSum` = 1 + 7 + 3 + 6 + 5 + 6 = 28.
| Index `i` | `nums[i]` | `leftSum` (start of loop) | `rightSum` Calculation | Is `leftSum == rightSum`? |
|---|---|---|---|---|
| 0 | 1 | 0 | `28 - 0 - 1 = 27` | No (0 != 27) |
| 1 | 7 | 1 | `28 - 1 - 7 = 20` | No (1 != 20) |
| 2 | 3 | 8 | `28 - 8 - 3 = 17` | No (8 != 17) |
| 3 | 6 | 11 | `28 - 11 - 6 = 11` | Yes (11 == 11). Return 3. |
The loop stops and returns index `3` because the sum of elements to the left (1 + 7 + 3 = 11) equals the sum of elements to the right (5 + 6 = 11).
3. Suffix & Combined Techniques
A suffix sum array is the mirror image of a prefix sum array. Each element `suffix[i]` stores the sum of all elements from index `i` to the end of the original array. This concept can be extended to products, maximums, or other calculations, and is powerful when combined with prefix calculations.
Creating a Suffix Sum Array
Formula:
- `suffix[n-1] = arr[n-1]`
- `suffix[i] = suffix[i+1] + arr[i]` for `i < n-1`
public class SuffixSumCreator {
public static int[] createSuffixSum(int[] arr) {
int n = arr.length;
int[] suffix = new int[n];
suffix[n - 1] = arr[n - 1];
for (int i = n - 2; i >= 0; i--) {
suffix[i] = suffix[i + 1] + arr[i];
}
return suffix;
}
}
Interview Problem: Product of Array Except Self
Problem: Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`. You must solve this in O(N) time and without using the division operator.
Approach using Prefix and Suffix Products
This is a classic problem that beautifully demonstrates the power of combining prefix and suffix computations. For any index `i`, the desired product is `(product of all elements to the left) * (product of all elements to the right)`.
- Create a `prefixProduct` array (we can use the `answer` array for this to save space). `answer[i]` will store the product of all elements before `i`.
- Calculate a `suffixProduct` on the fly. Iterate from the end of the array, multiply the current `answer[i]` by the `suffixProduct`, and then update the `suffixProduct`.
public class ProductExceptSelf {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
// Step 1: Calculate prefix products and store in the answer array
answer[0] = 1;
for (int i = 1; i < n; i++) {
answer[i] = answer[i - 1] * nums[i - 1];
}
// Step 2: Calculate suffix products and multiply with the prefix products
int suffixProduct = 1;
for (int i = n - 1; i >= 0; i--) {
answer[i] = answer[i] * suffixProduct;
suffixProduct *= nums[i];
}
return answer;
}
}
Dry Run: `nums = [1, 2, 3, 4]`
| Stage | `i` | Calculation | `answer` array state | `suffixProduct` |
|---|---|---|---|---|
| Prefix Pass | 0 | `answer[0] = 1` | `[1, _, _, _]` | - |
| 1 | `answer[1] = answer[0] * nums[0] = 1*1` | `[1, 1, _, _]` | - | |
| 2 | `answer[2] = answer[1] * nums[1] = 1*2` | `[1, 1, 2, _]` | - | |
| 3 | `answer[3] = answer[2] * nums[2] = 2*3` | `[1, 1, 2, 6]` | - | |
| --- Suffix Pass Begins --- | ||||
| Suffix Pass | 3 | `answer[3] = answer[3] * 1 = 6` `suffixProduct *= nums[3] = 1*4` | `[1, 1, 2, 6]` | 4 |
| 2 | `answer[2] = answer[2] * 4 = 8` `suffixProduct *= nums[2] = 4*3` | `[1, 1, 8, 6]` | 12 | |
| 1 | `answer[1] = answer[1] * 12 = 12` `suffixProduct *= nums[1] = 12*2` | `[1, 12, 8, 6]` | 24 | |
| 0 | `answer[0] = answer[0] * 24 = 24` `suffixProduct *= nums[0] = 24*1` | `[24, 12, 8, 6]` | 24 | |
Final Answer: [24, 12, 8, 6].
Interview Problem: Trapping Rain Water
Problem: Given `n` non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.
Approach using Prefix and Suffix Maximums
The amount of water trapped above any single bar depends on the height of the walls to its left and right. Specifically, the water level will be the `minimum` of the tallest bar to the left and the tallest bar to the right. The water trapped is this level minus the current bar's height.
- Create a `leftMax` array (prefix max): `leftMax[i]` stores the maximum height of a bar from index 0 to `i`.
- Create a `rightMax` array (suffix max): `rightMax[i]` stores the maximum height of a bar from index `i` to `n-1`.
- Iterate through the map. For each bar `i`, the trapped water is `min(leftMax[i], rightMax[i]) - height[i]`. Sum this up for all bars.
public class TrappingRainWater {
public int trap(int[] height) {
if (height == null || height.length == 0) return 0;
int n = height.length;
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int totalWater = 0;
for (int i = 0; i < n; i++) {
int waterLevel = Math.min(leftMax[i], rightMax[i]);
totalWater += waterLevel - height[i];
}
return totalWater;
}
}
Dry Run: `height = [0, 1, 0, 2, 1, 0]`
| Index `i` | `height[i]` | `leftMax[i]` | `rightMax[i]` | `min(L, R)` | Water (`min - h[i]`) |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 2 | 0 | 0 - 0 = 0 |
| 1 | 1 | 1 | 2 | 1 | 1 - 1 = 0 |
| 2 | 0 | 1 | 2 | 1 | 1 - 0 = 1 |
| 3 | 2 | 2 | 2 | 2 | 2 - 2 = 0 |
| 4 | 1 | 2 | 1 | 1 | 1 - 1 = 0 |
| 5 | 0 | 2 | 0 | 0 | 0 - 0 = 0 |
| Total Water: | 1 | ||||
The only water trapped is 1 unit at index 2. The total water trapped is 1.