mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-16 23:16:26 +00:00
120 lines
3.1 KiB
Plaintext
120 lines
3.1 KiB
Plaintext
---
|
||
title: 'Prefix Sum'
|
||
description: 'Precomputed cumulative state for O(1) range queries.'
|
||
---
|
||
|
||
## The idea
|
||
|
||
Precompute running totals once. Then any range sum costs O(1).
|
||
|
||
Like mile markers on a highway.
|
||
Distance from mile 30 to mile 80? Subtract: 80 − 30 = 50.
|
||
You do not re-drive the road.
|
||
|
||
**The formula:** `sum(i..j) = prefix[j + 1] - prefix[i]`
|
||
|
||
## The picture
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["nums: 3 1 4 1 5"] --> B["prefix: 0 3 4 8 9 14"]
|
||
B --> C["sum(1..3) = prefix[4] − prefix[1]<br/>= 9 − 3 = 6"]
|
||
C --> D["Check: 1 + 4 + 1 = 6 ✓"]
|
||
|
||
style D fill:#2e7d32,color:#fff
|
||
```
|
||
|
||
The leading 0 matters. It makes ranges that start at index 0 work with no special case.
|
||
|
||
## Range Sum Query (LC 303)
|
||
|
||
Pay O(n) once at build time. Answer every query in O(1).
|
||
|
||
```python
|
||
from itertools import accumulate
|
||
|
||
class NumArray:
|
||
def __init__(self, nums: list[int]):
|
||
self.prefix = [0] + list(accumulate(nums))
|
||
|
||
def sum_range(self, left: int, right: int) -> int:
|
||
return self.prefix[right + 1] - self.prefix[left]
|
||
```
|
||
|
||
## Prefix + Hashmap (LC 560 — Subarray Sum Equals K)
|
||
|
||
The signature trick of this topic. The question flips:
|
||
"Which subarrays sum to k?" becomes
|
||
"At each point, how many *earlier* prefixes equal `current − k`?"
|
||
|
||
Because: if `prefix[j] − prefix[i] = k`, the slice between them sums to k.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["Walk the array,<br/>carry running sum"] --> B["Ask the map:<br/>seen sum − k before?"]
|
||
B --> C["Yes, m times →<br/>add m to answer"]
|
||
B --> D["Record current sum<br/>in the map"]
|
||
D --> A
|
||
|
||
style C fill:#2e7d32,color:#fff
|
||
```
|
||
|
||
```python
|
||
from collections import defaultdict
|
||
|
||
def subarray_sum(nums: list[int], k: int) -> int:
|
||
count = 0
|
||
current = 0
|
||
seen = defaultdict(int)
|
||
seen[0] = 1 # empty prefix — subarrays that start at 0
|
||
|
||
for n in nums:
|
||
current += n
|
||
count += seen[current - k] # ask first
|
||
seen[current] += 1 # record after
|
||
return count
|
||
```
|
||
|
||
**Order matters.** Ask before you record, or a subarray of length 0 counts itself when k = 0.
|
||
|
||
## Product variant (LC 238 — Product of Array Except Self)
|
||
|
||
Same idea, two directions. Prefix products from the left, suffix products from the right.
|
||
`answer[i] = left[i] × right[i]` — everything except i.
|
||
|
||
```python
|
||
def product_except_self(nums: list[int]) -> list[int]:
|
||
n = len(nums)
|
||
result = [1] * n
|
||
|
||
left = 1
|
||
for i in range(n):
|
||
result[i] = left
|
||
left *= nums[i]
|
||
|
||
right = 1
|
||
for i in range(n - 1, -1, -1):
|
||
result[i] *= right
|
||
right *= nums[i]
|
||
return result
|
||
```
|
||
|
||
## Complexity
|
||
|
||
| | Time | Space |
|
||
|---|---|---|
|
||
| Build | O(n) | O(n) |
|
||
| Each range query | O(1) | — |
|
||
| Prefix + hashmap | O(n) one pass | O(n) |
|
||
|
||
## Close-out ritual
|
||
|
||
Before you submit, say out loud:
|
||
1. Time and space complexity.
|
||
2. One edge case trace: range starting at 0, k = 0 with zeros in the array, or negative numbers (this is why sliding window fails and prefix + hashmap wins).
|
||
|
||
## Plan problems
|
||
|
||
**A-set:** LC 303 · 238 · 560
|
||
**B-set:** LC 525 · 974
|