Prefix Sum — Worked Examples
Scope — The worked-solution archive behind prefix_sum.md: the eight problems the templates do not already solve end to end, grouped by which prefix-sum shape they need. See also: prefix_sum.md — the parent sheet: templates 1–8, the concept and the decision framework; prefix_sum_advanced.md — templates 9–14; difference_array.md — range updates in their own right, including LC 370; sliding_window.md — the alternative when all values are non-negative; hash_map.md — the structure four of these turn on.
LeetCode Problem Lists
Overview
This is the long tail of prefix_sum.md, and it is deliberately short. The parent sheet’s templates each name the LC problem they solve, so an example section that re-solved those problems was the file’s largest source of duplication — fourteen LC numbers appeared in more than one section heading, the worst count measured anywhere in the corpus.
What is left are the problems no template already works end to end.
Key Properties
- Complexity: every solution below is O(n) time after the prefix array is built, except LC 1292, which is O(m·n·log(min(m,n)))
- Core Idea: four of the eight are the same move — a hash map from prefix value to how many times it has been seen — applied to a different transform of the input
- When to Use: after the parent’s decision framework has named the template
Subarray Sums with a HashMap
1) Maximum Size Subarray Sum Equals k — LC 325
# LC 325. Maximum Size Subarray Sum Equals k
# V0
# time complexity : O(N) | space complexity : O(N)
# IDEA : HASH TBALE
# -> have a var acc keep sum of all item in nums,
# -> and use dic collect acc and its index
# -> since we want to find nums[i:j] = k -> so it's a 2 sum problem now
# -> i.e. if acc - k in dic => there must be a solution (i,j) of nums[i:j] = k
# -> return the max result
# -> ### acc DEMO : given array a = [1,2,3,4,5] ###
# -> acc_list = [1,3,6,10,15]
# -> so sum(a[1:3]) = 9 = acc_list[3] - acc_list[1-1] = 10 - 1 = 9
class Solution(object):
def maxSubArrayLen(self, nums, k):
result, acc = 0, 0
# NOTE !!! we init dic as {0:-1} ({sum:idx})
dic = {0: -1}
for i in range(len(nums)):
acc += nums[i]
if acc not in dic:
### NOTE : we save idx as dict value
dic[acc] = i
### acc - x = k -> so x = acc - k, that's why we check if acc - x in the dic or not
if acc - k in dic:
result = max(result, i - dic[acc-k])
return result
2) Continuous Subarray Sum — LC 523
// java
// LC 523
// V1
// IDEA : HASHMAP
// https://leetcode.com/problems/continuous-subarray-sum/editorial/
// https://github.com/yennanliu/CS_basics/blob/master/doc/pic/presum_mod.png
public boolean checkSubarraySum_1(int[] nums, int k) {
int prefixMod = 0;
HashMap<Integer, Integer> modSeen = new HashMap<>();
modSeen.put(0, -1);
for (int i = 0; i < nums.length; i++) {
/**
* NOTE !!! we get `mod of prefixSum`, instead of get prefixSum
*/
prefixMod = (prefixMod + nums[i]) % k;
if (modSeen.containsKey(prefixMod)) {
// ensures that the size of subarray is at least 2
if (i - modSeen.get(prefixMod) > 1) {
return true;
}
} else {
// mark the value of prefixMod with the current index.
modSeen.put(prefixMod, i);
}
}
return false;
}
3) Longest Well-Performing Interval — LC 1124
Pattern: HashMap + Prefix Sum — Longest Subarray with Positive Sum
Core Idea:
Transform each day: tiring (hours[i] > 8) → +1, non-tiring → -1. The problem becomes: find the longest subarray whose sum > 0.
At each index i with running prefix sum p:
Case 1: p > 0
→ entire interval [0..i] is valid
→ length = i + 1
Case 2: p ≤ 0
→ look for the earliest index j where prefix[j] = p - 1
→ subarray [j+1..i] has sum = p - (p-1) = 1 > 0
→ length = i - j
Why (p - 1)?
We want the LONGEST span ending at i with a net positive sum.
That means we need the SMALLEST prefix sum just one below the current value,
recorded at the EARLIEST index possible — hence putIfAbsent (first occurrence only).
Key Difference from Template 2:
- Template 2 stores
{prefix_sum: count}for counting subarrays. - This variant stores
{prefix_sum: first_index}for maximum length — only the first occurrence matters because an earlier start gives a longer interval.
Java Code:
// LC 1124 — Time: O(n), Space: O(n)
public int longestWPI(int[] hours) {
Map<Integer, Integer> map = new HashMap<>();
int prefix = 0, maxLen = 0;
for (int i = 0; i < hours.length; i++) {
prefix += hours[i] > 8 ? 1 : -1;
if (prefix > 0) {
maxLen = i + 1; // whole prefix is valid
} else {
if (map.containsKey(prefix - 1)) {
maxLen = Math.max(maxLen, i - map.get(prefix - 1));
}
}
map.putIfAbsent(prefix, i); // first occurrence only
}
return maxLen;
}
Similar LCs:
| Problem | LC # | Similarity |
|---|---|---|
| Contiguous Array | 525 | Longest subarray with equal 0s and 1s — same pattern, target sum = 0 |
| Maximum Size Subarray Sum Equals k | 325 | Longest subarray with sum = k, first-occurrence map |
| Subarray Sum Equals K | 560 | Count variant (store count, not index) |
| Binary Subarrays With Sum | 930 | Count subarrays with binary-transformed sum = k |
4) Flip String to Monotone Increasing — LC 926
# LC 926. Flip String to Monotone Increasing
# NOTE : there is also dp approaches
# V0
# IDEA : PREFIX SUM
class Solution(object):
def minFlipsMonoIncr(self, S):
# get pre-fix sum
P = [0]
for x in S:
P.append(P[-1] + int(x))
# find min
res = float('inf')
for j in range(len(P)):
res = min(res, P[j] + len(S)-j-(P[-1]-P[j]))
return res
# V1
# IDEA : PREFIX SUM
# https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/
class Solution(object):
def minFlipsMonoIncr(self, S):
# get pre-fix sum
P = [0]
for x in S:
P.append(P[-1] + int(x))
# return min
return min(P[j] + len(S)-j-(P[-1]-P[j])
for j in range(len(P)))
Fixed and Paired Windows
5) Maximum Sum of Two Non-Overlapping Subarrays — LC 1031
Core Idea (LC 1031):
Given two non-overlapping windows of fixed lengths L and M, maximize their combined sum.
Key Insight: one window must come before the other. Handle both orderings separately:
- Case 1: L-window appears before M-window
- Case 2: M-window appears before L-window
For each position i (right edge of the second window), track the maximum
sum of the first window seen so far (t), then combine with the current second window.
Prefix sum formula for a window of length W ending at index i (1-based):
window_sum = prefix[i] - prefix[i - W]
At each step:
t = max(t, prefix[i - M] - prefix[i - M - L]) ← best L-window before M starts
ans = max(ans, t + prefix[i] - prefix[i - M]) ← best L + current M
Why two passes? The two window orders (L before M, M before L) are
independent. The overall answer is max of both passes.
Pattern: Prefix Sum + Running Maximum (two-pass)
- Build prefix sum array once: O(n)
- For each pass, slide the second window right while maintaining
maxFirst(best first window so far) - Two passes cover all non-overlapping configurations
// java
// LC 1031 — Prefix Sum + Running Max
// time: O(N), space: O(N)
public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) {
int n = nums.length;
int[] s = new int[n + 1];
for (int i = 0; i < n; ++i) {
s[i + 1] = s[i] + nums[i];
}
int ans = 0;
// Case 1: firstLen window comes before secondLen window
// i is the right edge (exclusive) of the secondLen window
for (int i = firstLen, t = 0; i + secondLen - 1 < n; ++i) {
// best firstLen window that ends at or before position i (before M starts)
t = Math.max(t, s[i] - s[i - firstLen]);
// current secondLen window starting at i
ans = Math.max(ans, t + s[i + secondLen] - s[i]);
}
// Case 2: secondLen window comes before firstLen window
for (int i = secondLen, t = 0; i + firstLen - 1 < n; ++i) {
t = Math.max(t, s[i] - s[i - secondLen]);
ans = Math.max(ans, t + s[i + firstLen] - s[i]);
}
return ans;
}
Alternative helper-function style (cleaner):
// Calls helper(L before M) and helper(M before L), returns max
public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) {
int n = nums.length;
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
return Math.max(helper(prefix, firstLen, secondLen),
helper(prefix, secondLen, firstLen));
}
// L comes before M
private int helper(int[] prefix, int L, int M) {
int maxL = 0, res = 0;
for (int i = L + M; i < prefix.length; i++) {
// best L-window ending just before the M-window
maxL = Math.max(maxL, prefix[i - M] - prefix[i - M - L]);
// current M-window
res = Math.max(res, maxL + prefix[i] - prefix[i - M]);
}
return res;
}
Python (prefix sum + running max):
# python
# LC 1031 — Prefix Sum + Running Max
# time: O(N), space: O(N)
# ref: leetcode_python/Array/maximum-sum-of-two-non-overlapping-subarrays.py
class Solution:
def maxSumTwoNoOverlap(self, nums, firstLen, secondLen):
n = len(nums)
# prefix[i] = sum(nums[:i]) (size n+1, prefix[0] = 0 sentinel)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i + 1] = prefix[i] + nums[i]
# maxSum(L, M): best combined sum when the L-window is BEFORE the M-window
def maxSum(L, M):
# bestL = best L-window seen so far, ending before the current M-window
bestL = prefix[L] - prefix[0]
ans = 0
# i = starting index of the M-window
for i in range(L, n - M + 1):
# update best L-window ending at index i (i.e. nums[i-L:i])
bestL = max(bestL, prefix[i] - prefix[i - L])
# current M-window = nums[i:i+M]
currM = prefix[i + M] - prefix[i]
ans = max(ans, bestL + currM)
return ans
# try BOTH orders: L-before-M and M-before-L
return max(maxSum(firstLen, secondLen),
maxSum(secondLen, firstLen))
Why this is correct (core idea recap):
1. Core idea
- Two fixed-length windows (L and M) that must NOT overlap.
- One window is always fully to the left of the other, so enumerate
both orderings and take the max.
- Within one ordering, freeze the M-window's start (i), then the best
L-window is any L-window ending at/before i — track it as a running
max `bestL` so each i costs O(1).
2. Pattern
- Prefix Sum (O(1) window sum) + Running Maximum (best left window so far).
- Single left-to-right sweep per ordering → 2 sweeps total, O(n) each.
- window_sum for length W ending at index i: prefix[i] - prefix[i - W]
3. Similar LC → see table below
Similar LCs:
| Problem | LC # | Similarity |
|---|---|---|
| Maximum Subarray | 53 | Running max subarray (Kadane’s) |
| Best Time to Buy and Sell Stock III | 123 | Two non-overlapping operations, prefix+suffix |
| Maximum Sum of 3 Non-Overlapping Subarrays | 689 | Same pattern extended to 3 windows |
| Subarray Sum Equals K | 560 | Prefix sum + HashMap |
| Maximum Average Subarray II | 644 | Fixed/variable window with prefix sum |
2D Prefix Sums
6) Maximum Side Length of a Square with Sum ≤ Threshold — LC 1292
Pattern: 2D Prefix Sum + Binary Search or 2D Prefix Sum + Greedy
Core Idea:
- Build a 2D prefix sum table (size
(m+1) x (n+1)) so any square’s sum is computed in O(1). - Binary Search approach: Binary search on side length
[1, min(m,n)]. For each candidate lengthmid, scan all valid top-left corners and check if any square sum ≤ threshold. → O(m·n·log(min(m,n))) - Greedy approach: Single pass over all cells; at each cell
(i,j), only test if a square of sidemaxSide+1fits. If yes, incrementmaxSide. → O(m·n)
2D Prefix Sum formula (square ending at (i,j) with side k):
sum = P[i][j] - P[i-k][j] - P[i][j-k] + P[i-k][j-k]
Binary Search approach (Java):
// LC 1292 - V1 Binary Search
public int maxSideLength(int[][] mat, int threshold) {
int m = mat.length, n = mat[0].length;
int[][] P = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
P[i][j] = mat[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1];
int l = 1, r = Math.min(m, n), ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
boolean found = false;
outer:
for (int i = mid; i <= m; i++) {
for (int j = mid; j <= n; j++) {
int sum = P[i][j] - P[i-mid][j] - P[i][j-mid] + P[i-mid][j-mid];
if (sum <= threshold) { found = true; break outer; }
}
}
if (found) { ans = mid; l = mid + 1; }
else r = mid - 1;
}
return ans;
}
Greedy approach (Java):
// LC 1292 - V0 Greedy (O(m*n), optimal)
public int maxSideLength(int[][] mat, int threshold) {
int m = mat.length, n = mat[0].length;
int[][] P = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
P[i][j] = mat[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1];
int maxSide = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
int k = maxSide + 1; // only try to improve by 1
if (i >= k && j >= k) {
int sum = P[i][j] - P[i-k][j] - P[i][j-k] + P[i-k][j-k];
if (sum <= threshold) maxSide++;
}
}
}
return maxSide;
}
Why Greedy works: We only need to know the maximum achievable side length. Scanning left-to-right, top-to-bottom ensures we never miss a valid square — if a larger square exists somewhere, it will be discovered when we reach its bottom-right corner.
Similar LCs:
| Problem | LC # | Similarity |
|---|---|---|
| Range Sum Query 2D | 304 | Core 2D prefix sum template |
| Matrix Block Sum | 1314 | Fixed-radius 2D range query |
| Number of Submatrices That Sum to Target | 1074 | 2D prefix sum + count (harder) |
| Maximal Square | 221 | Max square in matrix (DP approach) |
| Largest 1-Bordered Square | 1139 | Max square with border condition |
Range Updates
7) Range Addition II / prefix on a difference array — LC 1094
// java
// LC 1094
// ...
int[] prefixSum = new int[1001]; // the biggest array size given by problem
// `init pre prefix sum`
for (int[] t : trips) {
int amount = t[0];
int start = t[1];
int end = t[2];
/**
* NOTE !!!!
*
* via trick below, we can `efficiently` setup prefix sum
* per start, end index
*
* -> we ADD amount at start point (customer pickup up)
* -> we MINUS amount at `end point` (customer drop off)
*
* -> via above, we get the `adjusted` `init prefix sum`
* -> so all we need to do next is :
* -> loop over the `init prefix sum`
* -> and keep adding `previous to current val`
* -> e.g. prefixSum[i] = prefixSum[i-1] + prefixSum[i]
*
*/
prefixSum[start] += amount;
prefixSum[end] -= amount;
}
// update `prefix sum` array
for (int i = 1; i < prefixSum.length; i++) {
prefixSum[i] += prefixSum[i - 1];
}
// ...
Prefix Max / Suffix Min Scans
8) Sum of Beauty in the Array — LC 2012
Core Idea (LC 2012):
For each i in [1, n-2] the beauty of nums[i] is:
2 if nums[i] beats EVERY element to its left and loses to EVERY element to its right
1 else if it only beats its two neighbours: nums[i-1] < nums[i] < nums[i+1]
0 otherwise
The 2-test is a global question asked at every index — O(n) per index if asked
literally. Two extreme scans reduce it to O(1) per index:
prefixMax[i] = max(nums[0 .. i-1]) <- everything STRICTLY left of i
suffixMin[i] = min(nums[i+1 .. n-1]) <- everything STRICTLY right of i
beauty 2 <=> prefixMax[i] < nums[i] < suffixMin[i]
Only the suffix side has to be materialised: it is known only after a
right-to-left pass. The prefix side is a single rolling variable, because the
left-to-right loop has already walked past everything it needs.
Pattern: materialised suffix extreme + rolling prefix extreme
- One right-to-left pass builds
suffixMin— O(n) time, O(n) space - One left-to-right pass answers both tests and updates
prefixMax— O(n) time, O(1) extra - The beauty-2 test is the LC 768 cut test (
prefixMax < suffixMin) asked about a single element instead of a cut point
Visual trace — nums = [2, 4, 6, 4]:
index 0 1 2 3
nums 2 4 6 4
prefixMax - 2 4 - (max of everything strictly left)
suffixMin - 4 4 - (min of everything strictly right)
i = 1: 2 < 4 < 4 ? no (4 < 4 fails) -> not 2
nums[0]=2 < 4 < nums[2]=6 ? yes -> +1
i = 2: 4 < 6 < 4 ? no -> not 2
nums[1]=4 < 6 < nums[3]=4 ? no -> +0
answer = 1
# python
# LC 2012 — Suffix Min array + rolling Prefix Max
# time: O(N), space: O(N)
# ref: leetcode_python/prefix_sum/sum-of-beauty-in-the-array.py
class Solution:
def sumOfBeauties(self, nums):
n = len(nums)
# suffix_min[i] = min(nums[i+1 .. n-1]) (strictly to the right of i)
# NOTE: inf at the last index, so the "nothing to the right" case
# never blocks the comparison
suffix_min = [float("inf")] * n
for i in range(n - 2, -1, -1):
suffix_min[i] = min(suffix_min[i + 1], nums[i + 1])
res = 0
prefix_max = nums[0] # max(nums[0 .. i-1]) as the loop reaches i
for i in range(1, n - 1):
# beauty 2: global — beats all of the left, loses to all of the right
if prefix_max < nums[i] < suffix_min[i]:
res += 2
# beauty 1: local — only the two neighbours
# NOTE: `elif`, since the 2-case already implies this one
elif nums[i - 1] < nums[i] < nums[i + 1]:
res += 1
prefix_max = max(prefix_max, nums[i])
return res
// java
// LC 2012 — Suffix Min array + rolling Prefix Max
// time: O(N), space: O(N)
public int sumOfBeauties(int[] nums) {
int n = nums.length;
// suffixMin[i] = min(nums[i+1 .. n-1])
int[] suffixMin = new int[n];
suffixMin[n - 1] = Integer.MAX_VALUE;
for (int i = n - 2; i >= 0; i--) {
suffixMin[i] = Math.min(suffixMin[i + 1], nums[i + 1]);
}
int res = 0;
int prefixMax = nums[0];
for (int i = 1; i < n - 1; i++) {
if (prefixMax < nums[i] && nums[i] < suffixMin[i]) {
res += 2;
} else if (nums[i - 1] < nums[i] && nums[i] < nums[i + 1]) {
res += 1;
}
prefixMax = Math.max(prefixMax, nums[i]);
}
return res;
}
The two traps:
| Trap | What goes wrong |
|---|---|
| Off-by-one on the extremes | Both windows are strict. Building suffixMin[i] = min(suffixMin[i+1], nums[i]) includes nums[i] itself, so it must then be read as suffixMin[i+1]. Building it from nums[i+1] (above) lets it be read at i. Mixing the two spellings makes every beauty-2 test compare nums[i] against itself and the answer collapses. |
if instead of elif |
An element with beauty 2 also satisfies the neighbour test, so a second if scores it 3. |
Why the prefix side needs no array.
prefix_maxis only ever read at the index the loop is currently on, and it is updated at the end of each iteration — so the value in hand is alwaysmax(nums[0 .. i-1]). The suffix side is read before the pass that would compute it, which is the whole reason one of the two directions has to be stored.
Similar problems:
| Problem | LC # | Key difference |
|---|---|---|
| Max Chunks To Make Sorted II | 768 | Same prefixMax < suffixMin test, asked at cut points instead of elements — Template 8 |
| Max Chunks To Make Sorted | 769 | Permutation input, so prefixMax == i replaces the suffix array entirely — Template 8 |
| Trapping Rain Water | 42 | Prefix max and suffix max, both materialised, combined by min(...) - height[i] |
| Product of Array Except Self | 238 | Same left-pass / right-pass split with products instead of extremes |