248 lines
7.8 KiB
YAML
248 lines
7.8 KiB
YAML
name: Prefix Sum
|
|
slug: prefix-sum
|
|
difficulty_level: 2
|
|
pattern_type: technique
|
|
display_order: 5
|
|
|
|
description: >
|
|
Precompute cumulative sums to answer range sum queries in O(1) time. This
|
|
transforms repeated O(n) range calculations into O(n) preprocessing plus
|
|
O(1) per query, making it essential for problems involving subarray sums.
|
|
|
|
when_to_use: |
|
|
- Range sum queries
|
|
- Subarray sum equals target
|
|
- Count subarrays with given sum
|
|
- Product of array except self
|
|
- 2D matrix region sums
|
|
|
|
metaphor: |
|
|
Imagine tracking your total running distance over a year. Instead of adding up
|
|
daily distances each time someone asks "how far did you run from day 50 to day
|
|
75?", you keep a running total. The cumulative distance on day 75 minus day 49
|
|
instantly gives you the answer.
|
|
|
|
Another analogy: a bank account balance. To find how much you spent between
|
|
two dates, you subtract the earlier balance from the later balance—no need to
|
|
sum individual transactions.
|
|
|
|
core_concept: |
|
|
A **prefix sum array** stores cumulative sums where `prefix[i]` = sum of all
|
|
elements from index 0 to i-1. This enables O(1) range sum queries:
|
|
|
|
**sum(i, j) = prefix[j+1] - prefix[i]**
|
|
|
|
The key insight is that any range sum can be computed from two prefix sums.
|
|
Instead of iterating through the range (O(n) per query), we do O(n) preprocessing
|
|
once and answer unlimited queries in O(1) each.
|
|
|
|
This pattern extends to:
|
|
- **Prefix products**: For multiplication-based problems
|
|
- **Prefix counts**: For counting occurrences
|
|
- **2D prefix sums**: For matrix region queries
|
|
- **Prefix sum + hash map**: For "subarray sum equals K"
|
|
|
|
visualization: |
|
|
**Building prefix sum array:**
|
|
|
|
```
|
|
Array: [3, 1, 4, 1, 5, 9]
|
|
Index: 0 1 2 3 4 5
|
|
|
|
prefix[0] = 0 (empty prefix)
|
|
prefix[1] = 0 + 3 = 3 (sum of first 1 element)
|
|
prefix[2] = 3 + 1 = 4 (sum of first 2 elements)
|
|
prefix[3] = 4 + 4 = 8
|
|
prefix[4] = 8 + 1 = 9
|
|
prefix[5] = 9 + 5 = 14
|
|
prefix[6] = 14 + 9 = 23
|
|
|
|
Prefix: [0, 3, 4, 8, 9, 14, 23]
|
|
Index: 0 1 2 3 4 5 6
|
|
```
|
|
|
|
**Range sum query: sum(2, 4) = elements at indices 2, 3, 4**
|
|
|
|
```
|
|
sum(2, 4) = prefix[5] - prefix[2]
|
|
= 14 - 4
|
|
= 10
|
|
|
|
Verification: arr[2] + arr[3] + arr[4] = 4 + 1 + 5 = 10 ✓
|
|
```
|
|
|
|
**Subarray sum equals K using hash map:**
|
|
|
|
```
|
|
Array: [1, 2, 3, -2, 5] K = 4
|
|
|
|
As we iterate, track prefix sums and look for prefix_sum - K:
|
|
|
|
i=0: prefix=1, need 1-4=-3, not found
|
|
i=1: prefix=3, need 3-4=-1, not found
|
|
i=2: prefix=6, need 6-4=2, not found
|
|
i=3: prefix=4, need 4-4=0, found! (empty prefix)
|
|
→ subarray [0:4] sums to 4
|
|
i=4: prefix=9, need 9-4=5, not found
|
|
|
|
Wait, also: prefix at i=1 is 3, at i=4 is 9-4=5... let me recalculate
|
|
Actually arr[1:3] = 2+3-2=3, arr[2:4]=3-2+5=6... checking for K=4:
|
|
subarray [0:4] → 1+2+3-2=4 ✓
|
|
```
|
|
|
|
code_template: |
|
|
def build_prefix_sum(arr: list[int]) -> list[int]:
|
|
"""Build prefix sum array. prefix[i] = sum of arr[0:i]."""
|
|
prefix = [0] * (len(arr) + 1)
|
|
for i in range(len(arr)):
|
|
prefix[i + 1] = prefix[i] + arr[i]
|
|
return prefix
|
|
|
|
|
|
def range_sum(prefix: list[int], i: int, j: int) -> int:
|
|
"""Sum of elements from index i to j (inclusive)."""
|
|
return prefix[j + 1] - prefix[i]
|
|
|
|
|
|
def subarray_sum_equals_k(nums: list[int], k: int) -> int:
|
|
"""Count subarrays with sum equal to k."""
|
|
count = 0
|
|
prefix_sum = 0
|
|
# Map: prefix_sum -> count of occurrences
|
|
sum_count = {0: 1} # Empty prefix has sum 0
|
|
|
|
for num in nums:
|
|
prefix_sum += num
|
|
|
|
# If (prefix_sum - k) exists, those prefixes form valid subarrays
|
|
if prefix_sum - k in sum_count:
|
|
count += sum_count[prefix_sum - k]
|
|
|
|
# Record current prefix sum
|
|
sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
|
|
|
|
return count
|
|
|
|
|
|
def product_except_self(nums: list[int]) -> list[int]:
|
|
"""Product of array except self without division."""
|
|
n = len(nums)
|
|
result = [1] * n
|
|
|
|
# Prefix products (left to right)
|
|
prefix = 1
|
|
for i in range(n):
|
|
result[i] = prefix
|
|
prefix *= nums[i]
|
|
|
|
# Suffix products (right to left)
|
|
suffix = 1
|
|
for i in range(n - 1, -1, -1):
|
|
result[i] *= suffix
|
|
suffix *= nums[i]
|
|
|
|
return result
|
|
|
|
|
|
def matrix_region_sum(matrix: list[list[int]],
|
|
row1: int, col1: int,
|
|
row2: int, col2: int) -> int:
|
|
"""2D prefix sum for matrix region queries."""
|
|
# Build 2D prefix sum
|
|
m, n = len(matrix), len(matrix[0])
|
|
prefix = [[0] * (n + 1) for _ in range(m + 1)]
|
|
|
|
for i in range(1, m + 1):
|
|
for j in range(1, n + 1):
|
|
prefix[i][j] = (matrix[i-1][j-1]
|
|
+ prefix[i-1][j]
|
|
+ prefix[i][j-1]
|
|
- prefix[i-1][j-1])
|
|
|
|
# Query region sum using inclusion-exclusion
|
|
return (prefix[row2+1][col2+1]
|
|
- prefix[row1][col2+1]
|
|
- prefix[row2+1][col1]
|
|
+ prefix[row1][col1])
|
|
|
|
recognition_signals:
|
|
- "range sum"
|
|
- "subarray sum"
|
|
- "cumulative"
|
|
- "sum equals k"
|
|
- "count subarrays"
|
|
- "product except self"
|
|
- "matrix region sum"
|
|
- "running total"
|
|
- "continuous subarray"
|
|
|
|
common_mistakes:
|
|
- title: Off-by-one in prefix array indexing
|
|
description: |
|
|
Confusion about whether prefix[i] includes arr[i] or not leads to
|
|
incorrect range sums.
|
|
fix: |
|
|
Convention: `prefix[i]` = sum of first i elements = `arr[0:i]`.
|
|
So `prefix[0] = 0` (empty), and range sum is `prefix[j+1] - prefix[i]`.
|
|
|
|
- title: Forgetting the empty prefix for "sum equals K"
|
|
description: |
|
|
Not initializing the hash map with `{0: 1}` misses subarrays starting
|
|
from index 0.
|
|
fix: |
|
|
Always initialize: `sum_count = {0: 1}`. This handles the case where the
|
|
subarray from the beginning has sum K.
|
|
|
|
- title: Integer overflow with large sums
|
|
description: |
|
|
Prefix sums can grow very large when array elements are big, causing
|
|
overflow in some languages.
|
|
fix: |
|
|
In Python this isn't an issue. In Java/C++, use `long` for prefix sums
|
|
or check constraints carefully.
|
|
|
|
- title: Not handling negative numbers
|
|
description: |
|
|
Prefix sum works with negative numbers, but some may expect only positive
|
|
sums and use wrong optimization (like sliding window).
|
|
fix: |
|
|
Prefix sum handles negatives correctly. But problems like "minimum sum
|
|
subarray of size k" need different approaches when negatives are present.
|
|
|
|
variations:
|
|
- name: Basic prefix sum
|
|
description: |
|
|
Precompute cumulative sums for O(1) range queries. Foundation for other
|
|
variations.
|
|
example: "Range Sum Query - Immutable, Running Sum of 1d Array"
|
|
|
|
- name: Prefix sum with hash map
|
|
description: |
|
|
Track prefix sums in a hash map to find subarrays summing to a target.
|
|
Key insight: `prefix[j] - prefix[i] = target` means subarray [i,j] works.
|
|
example: "Subarray Sum Equals K, Contiguous Array"
|
|
|
|
- name: Prefix product
|
|
description: |
|
|
Same idea but with multiplication. Watch for zeros and consider using
|
|
left/right products separately.
|
|
example: "Product of Array Except Self"
|
|
|
|
- name: 2D prefix sum
|
|
description: |
|
|
Extend to matrices for O(1) region sum queries. Uses inclusion-exclusion
|
|
principle.
|
|
example: "Range Sum Query 2D, Matrix Block Sum"
|
|
|
|
- name: Difference array
|
|
description: |
|
|
Inverse of prefix sum. Store differences to support O(1) range updates.
|
|
Prefix sum of difference array gives original.
|
|
example: "Range Addition, Corporate Flight Bookings"
|
|
|
|
related_patterns:
|
|
- sliding-window
|
|
- two-pointers
|
|
|
|
prerequisite_patterns: []
|