{}DSA Atlas

Prefix Sums & Difference Arrays

Precompute cumulative totals to answer range queries in O(1), count subarrays with a target sum (even with negatives), and apply range updates with difference arrays.

Intermediate14 practice problems3 easy9 medium2 hard

The idea

Precompute P[i] = a[0] + a[1] + … + a[i−1] (with P[0] = 0). Then the sum of any range is a subtraction:

Text
sum(a[l..r]) = P[r + 1] − P[l]

O(n) preprocessing, O(1) per query. The real power, though, is that it turns “subarray sum” questions into “pair of prefix values” questions, which hash maps and sorting can answer.

Template: 1D prefix sums

def build_prefix(a):
    P = [0] * (len(a) + 1)
    for i, x in enumerate(a):
        P[i + 1] = P[i] + x
    return P

# sum of a[l..r] inclusive
def range_sum(P, l, r):
    return P[r + 1] - P[l]

# Python shortcut
from itertools import accumulate
P = [0, *accumulate(a)]
vector<long long> P(n + 1, 0);
for (int i = 0; i < n; i++) P[i + 1] = P[i] + a[i];
// sum of a[l..r] inclusive
auto rangeSum = [&](int l, int r) { return P[r + 1] - P[l]; };

Pattern: prefix sum + hash map

A subarray (i, j] sums to k exactly when P[j] − P[i] = k, i.e. P[i] = P[j] − k. Scan j left to right, keeping a hash map of prefix values seen so far.

Count subarrays with sum k

def subarray_sum(nums, k):
    seen = {0: 1}                # the empty prefix
    P = count = 0
    for x in nums:
        P += x
        count += seen.get(P - k, 0)
        seen[P] = seen.get(P, 0) + 1
    return count
int subarraySum(vector<int>& nums, int k) {
    unordered_map<long long, int> seen{{0, 1}};
    long long P = 0; int count = 0;
    for (int x : nums) {
        P += x;
        auto it = seen.find(P - k);
        if (it != seen.end()) count += it->second;
        seen[P]++;
    }
    return count;
}

Longest subarray with sum k

Store the first index where each prefix value appears (to maximize length).

Python
def max_len_sum_k(nums, k):
    first = {0: -1}
    P = best = 0
    for i, x in enumerate(nums):
        P += x
        if P - k in first:
            best = max(best, i - first[P - k])
        first.setdefault(P, i)        # keep the earliest
    return best

Reductions to this template:

ProblemTransform
Equal 0s and 1s (Contiguous Array)treat 0 as −1, find longest sum 0
Divisible by kkey by P % k (normalize: ((P % k) + k) % k in C++)
Equal count of two letters / vowels balanced+1 / −1 encoding, or key by a tuple of differences
Subarray XOR equals kprefix XOR; look up P ^ k
Parity of each vowel (Longest substring with even vowels)prefix bitmask, look up same mask

Subarrays divisible by k

Python
def subarrays_div_by_k(nums, k):
    count = [0] * k
    count[0] = 1
    P = res = 0
    for x in nums:
        P = (P + x) % k          # Python % is always non-negative
        res += count[P]
        count[P] += 1
    return res

2D prefix sums

S[i][j] = sum of the rectangle from (0,0) to (i−1, j−1). Inclusion–exclusion builds and queries it.

def build_2d(M):
    R, C = len(M), len(M[0])
    S = [[0] * (C + 1) for _ in range(R + 1)]
    for i in range(R):
        for j in range(C):
            S[i + 1][j + 1] = M[i][j] + S[i][j + 1] + S[i + 1][j] - S[i][j]
    return S

def rect_sum(S, r1, c1, r2, c2):      # inclusive corners
    return S[r2 + 1][c2 + 1] - S[r1][c2 + 1] - S[r2 + 1][c1] + S[r1][c1]
vector<vector<long long>> S(R + 1, vector<long long>(C + 1, 0));
for (int i = 0; i < R; i++)
    for (int j = 0; j < C; j++)
        S[i + 1][j + 1] = M[i][j] + S[i][j + 1] + S[i + 1][j] - S[i][j];
auto rect = [&](int r1, int c1, int r2, int c2) {
    return S[r2 + 1][c2 + 1] - S[r1][c2 + 1] - S[r2 + 1][c1] + S[r1][c1];
};

Submatrix sum equals target: fix a pair of rows (O(R²)), collapse columns into a 1D array of column sums, and run the 1D hash-map count → O(R²·C).

Difference arrays: range updates in O(1)

The inverse of prefix sums. To add x to every element in [l, r]:

Text
diff[l] += x
diff[r + 1] -= x

After all updates, a prefix sum over diff reconstructs the final array. k updates on n elements: O(n + k) instead of O(n·k).

def apply_updates(n, updates):          # updates: (l, r, x), inclusive
    diff = [0] * (n + 1)
    for l, r, x in updates:
        diff[l] += x
        diff[r + 1] -= x
    out, running = [], 0
    for i in range(n):
        running += diff[i]
        out.append(running)
    return out
vector<long long> applyUpdates(int n, const vector<array<int, 3>>& updates) {
    vector<long long> diff(n + 1, 0), out(n);
    for (auto& [l, r, x] : updates) { diff[l] += x; diff[r + 1] -= x; }
    long long running = 0;
    for (int i = 0; i < n; i++) { running += diff[i]; out[i] = running; }
    return out;
}

This is the sweep line idea in disguise: Car Pooling, Meeting Rooms II (max overlap), My Calendar, Corporate Flight Bookings. When coordinates are huge, use a sorted map of events instead of an array.

2D difference arrays work the same way with four corner updates: d[r1][c1] += x; d[r1][c2+1] -= x; d[r2+1][c1] -= x; d[r2+1][c2+1] += x, then a 2D prefix sum.

Beyond sums

Anything invertible works like a prefix sum:

  • Prefix XOR: xor(l..r) = PX[r+1] ^ PX[l].
  • Prefix counts: count of 'a' in s[l..r] with one prefix array per letter.
  • Prefix products (no zeros, or handle zeros separately).

Things that are not invertible — min, max, gcd — need a sparse table or segment tree for range queries.

When values can be negative and you want “sum ≥ k”

Sliding window fails, and a hash map only answers “= k”. For shortest subarray with sum ≥ k, keep a monotonic increasing deque of prefix indices:

Python
from collections import deque

def shortest_subarray(nums, k):
    P = [0]
    for x in nums:
        P.append(P[-1] + x)
    dq, best = deque(), float("inf")
    for j in range(len(P)):
        while dq and P[j] - P[dq[0]] >= k:
            best = min(best, j - dq.popleft())    # front can't do better later
        while dq and P[dq[-1]] >= P[j]:
            dq.pop()                               # a later, smaller prefix dominates
        dq.append(j)
    return best if best != float("inf") else -1

Common mistakes

  • Off-by-one between P[r] and P[r+1]. With the leading zero: sum(l..r) = P[r+1] − P[l].
  • Forgetting to seed the hash map with prefix 0 (misses subarrays starting at index 0).
  • Negative modulo in C++/Java: (-3) % 5 == -3. Normalize with ((x % k) + k) % k.
  • Overflow: prefix sums of 10⁵ values up to 10⁹ need 64-bit integers.
  • Updating the map before querying it when a subarray must be non-empty.

Practice problems

Ordered roughly from warm-up to hard. Try each one for 25–40 minutes before peeking at the key idea; if you needed the hint, mark it “review” and redo it in a few days.

0 / 14 solved
prefix[i] = prefix[i−1] + nums[i].
Easy
prefix of length n+1; sum(l..r) = P[r+1] − P[l].
Easy
Left sum == total − left sum − nums[i].
Easy
Hash map of prefix counts; add count[P − k]; seed count[0] = 1.
Medium
Store first index of each prefix mod k; same remainder at distance >= 2 → yes.
Medium
Count prefixes by remainder (normalize negatives); pairs with equal remainder.
Medium
Map 0 → −1; longest subarray with sum 0 = earliest index of each prefix value.
Medium
2D prefix with inclusion–exclusion.
Medium
Difference array: diff[l] += x, diff[r+1] −= x, then prefix-sum.
Medium
Difference array over locations; running sum must never exceed capacity.
Medium
Prefix products times suffix products.
Medium
Earliest index of each prefix; length = i − first[P − k].
Medium
Negatives allowed: monotonic increasing deque of prefix indices; pop front while P[i] − P[front] >= k.
Hard
Count pairs of prefixes with P[j] − P[i] in [lower, upper] via merge sort or a Fenwick tree.
Hard

Click the box to cycle: solved ✓ → needs review ↺ → not started. Links open LeetCode.