DSA DSA In Depth
0%
lesson prefix-sumsdifference-arrayarrays

1D/2D Prefix Sums & Difference Arrays for O(1) Range Updates

7 min read Module 1.2: Contiguous Arrays, Bit Manipulation & Pointer Idioms Updated Oct 23, 2026
📄 Cheat Sheet
Prefix Sums — Problems, Invariants & Implementation in C++ and Python
Errichto Algorithms Prefix Sums — Problems, Invariants & Implementation in C++ and Python Watch on YouTube ↗
0:00 / --:--

The Discrete Fundamental Theorem of Calculus

Given an array $A[0 \dots N-1]$ over an abelian group (such as $(\mathbb{Z}, +)$ or bitvectors under $\oplus$), define the 1-indexed prefix sum array $P[0 \dots N]$ with sentinel $P[0] = 0$ and recurrence $P[i] = P[i-1] + A[i-1]$ for $1 \le i \le N$. Any contiguous range sum over the inclusive 0-indexed interval $[L, R]$ reduces to a single subtraction:

$$\sum_{k=L}^{R} A[k] = P[R + 1] - P[L]$$

Notice why the $N+1$ length with sentinel $P[0] = 0$ is mandatory in clean systems code: it eliminates the branch if (L == 0) on every query. Conversely, define the Difference Array (discrete derivative) $D[0 \dots N]$ where $D[0] = A[0]$ and $D[i] = A[i] - A[i-1]$ for $1 \le i < N$. The prefix sum of $D$ reconstructs $A$:

$$A[i] = \sum_{k=0}^{i} D[k]$$

Because every element $A[i]$ for $i \in [L, R]$ includes $D[L]$ in its prefix sum, adding a scalar $\Delta$ to the entire subarray $A[L \dots R]$ only modifies two adjacent boundaries in $D$: $D[L] \mathrel{+}= \Delta$ and $D[R+1] \mathrel{-}= \Delta$.

Mathematical Invariant: 2D Inclusion-Exclusion & 4-Corner Differential Stencils

In two dimensions, given matrix $A[0 \dots R-1][0 \dots C-1]$, the 1-indexed 2D prefix sum $P[r][c]$ stores the sum of the subgrid $[0 \dots r-1] \times [0 \dots c-1]$. By the Principle of Inclusion-Exclusion:

$$P[r][c] = A[r-1][c-1] + P[r-1][c] + P[r][c-1] - P[r-1][c-1]$$

Querying any subrectangle $[r_1, r_2] \times [c_1, c_2]$ takes $O(1)$ time via four table lookups:

$$\text{Sum}(r_1, c_1, r_2, c_2) = P[r_2+1][c_2+1] - P[r_1][c_2+1] - P[r_2+1][c_1] + P[r_1][c_1]$$

Dually, adding $\Delta$ to every cell in $[r_1, r_2] \times [c_1, c_2]$ of a 2D difference array $D$ updates 4 corners in $O(1)$: $D[r_1][c_1] \mathrel{+}= \Delta$, $D[r_1][c_2+1] \mathrel{-}= \Delta$, $D[r_2+1][c_1] \mathrel{-}= \Delta$, and $D[r_2+1][c_2+1] \mathrel{+}= \Delta$.

Trade-off Matrix: Static vs. Batched vs. Dynamic Range Operations

Structure Precomputation Range Sum Query $[L, R]$ Range Add Update $[L, R] + \Delta$ Point Query $A[i]$ Ideal Workload Profile
Raw Array $A$ $O(1)$ $O(N)$ $O(N)$ $O(1)$ Point reads and point writes only
1D / 2D Prefix Sum $P$ $O(N)$ / $O(RC)$ $O(1)$ $O(N)$ (rebuilds $P$) $O(1)$ Immutable array, millions of range queries
Difference Array $D$ $O(N)$ / $O(RC)$ $O(N)$ (after reconstruction) $O(1)$ $O(N)$ online / $O(1)$ offline Batched offline range updates, then readout
Fenwick Tree (BIT) $O(N)$ $O(\log N)$ $O(\log N)$ (with 2 BITs) $O(\log N)$ Interleaved online updates and queries

Visualizing 2D Inclusion-Exclusion & Difference Array Boundaries

2D Prefix Sum: Sum(r1,c1, r2,c2) = P[r2+1][c2+1] - Top - Left + Corner +P[r1][c1] (Subtracted 2x) -P[r1][c2+1] (Top) -P[r2+1][c1] Target Subgrid [r1..r2] x [c1..c2] 1D Difference Array: Add +5 to Range [L=1, R=3] Diff D[]: 0 +5 (L) 0 0 -5 (R+1) Prefix(D): 0 5 5 5 0 2D Diff Stencil for [r1..r2, c1..c2]: D[r1][c1] += d, D[r1][c2+1] -= d, D[r2+1][c1] -= d, D[r2+1][c2+1] += d

Prefix Sum + Hash Map Pattern for Subarray Sum Equals $K$

flowchart LR A["Iterate j = 0 .. N-1
running_sum += A[j]"] --> B["Need P[j+1] - P[i] == K
iff P[i] == running_sum - K"] B --> C{"Is (running_sum - K)
in freq_map?"} C -- "Yes" --> D["count += freq_map[running_sum - K]"] C -- "No" --> E["freq_map[running_sum]++"] D --> E
Hardware Reality: SIMD Prefix Sums (Hillis-Steele Scan) & Flat 2D Row-Major Storage

At first glance, the recurrence $P[i] = P[i-1] + A[i]$ looks inherently serial due to its loop-carried dependency of distance 1. However, because integer addition is associative, modern CPUs and GPUs compute prefix sums using the Hillis-Steele / Blelloch parallel scan inside SIMD registers: within a 256-bit AVX2 register holding eight 32-bit integers, three shift-and-add steps (_mm256_slli_si256 + _mm256_add_epi32 by 1, 2, and 4 lanes) compute the prefix sum of 8 elements in $O(\log_2 W)$ instructions. Furthermore, in 2D prefix sums, never allocate a jagged std::vector<std::vector<int64_t>>; each row is a separate heap allocation that causes L1 TLB misses when reading $P[r-1][c]$. Flatten the matrix into a single contiguous array of length $(R+1)(C+1)$ indexed via r * (C + 1) + c.

2D Prefix Inclusion-Exclusion, Difference Array & Kadane Subarray
O(1) Range Query / O(N) Kadane

Trace 2D prefix-sum inclusion-exclusion P[r2][c2] - P[r1-1][c2] - P[r2][c1-1] + P[r1-1][c1-1], O(1) difference-array range updates, and Kadane circular max subarray.

Step 1 / 7
Custom Input:
Timeline

Invariant & State

Kadane Circular Duality: A wrapping subarray equals the complement of a contiguous middle subarray, so max_wrap = TotalSum - minSubarray.
📊 Live Magnitude & Pointer Stage — 2D Prefix Inclusion-Exclusion, Difference Array & Kadane Subarray Active Pivot Match/Sorted
Prefix 5 [0] Min Sub -3 [1] Suffix 5 [2] Total(7 Circul… [3]
[0]5Prefix
[1]-3Min Subarray = -3
[2]5Suffix
[3]Circular Wrap: [5] + [5] = 10Total(7) - Min(-3) = 10
Standard Kadane Max
7 ([5, -3, 5])
Minimum Subarray
-3 ([-3])
Circular Max (Total - Min)
7 - (-3) = 10
[Step 1] Computed both maxSubarray=7 and minSubarray=-3; circular wrap achieves Total - Min = 10.

Production Implementation: Contiguous 2D Prefix Sum & 2D Difference Stencil

#include <cstdint>
#include <vector>

class FlatGrid2D {
  std::size_t rows_;
  std::size_t cols_;
  std::vector<std::int64_t> data_; // (rows_ + 1) * (cols_ + 2) for safe sentinel padding

  std::size_t idx(std::size_t r, std::size_t c) const noexcept {
    return r * (cols_ + 2) + c;
  }

public:
  FlatGrid2D(std::size_t rows, std::size_t cols)
      : rows_(rows), cols_(cols), data_((rows + 2) * (cols + 2), 0) {}

  // O(1) 2D Difference Array range update on 0-indexed inclusive [r1..r2] x [c1..c2]
  void range_add(std::size_t r1, std::size_t c1, std::size_t r2, std::size_t c2, std::int64_t delta) noexcept {
    // Shift to 1-indexed internal coordinates
    ++r1; ++c1; ++r2; ++c2;
    data_[idx(r1, c1)]         += delta;
    data_[idx(r1, c2 + 1)]     -= delta;
    data_[idx(r2 + 1, c1)]     -= delta;
    data_[idx(r2 + 1, c2 + 1)] += delta;
  }

  // O(R * C) in-place 2D prefix sum reconstruction (call once to materialize values,
  // or twice to turn a 2D difference array directly into a 2D prefix sum query table!)
  void materialize_prefix_sums() noexcept {
    for (std::size_t r = 1; r <= rows_; ++r) {
      for (std::size_t c = 1; c <= cols_; ++c) {
        data_[idx(r, c)] += data_[idx(r - 1, c)]
                          + data_[idx(r, c - 1)]
                          - data_[idx(r - 1, c - 1)];
      }
    }
  }

  // O(1) subrectangle sum query on 0-indexed inclusive [r1..r2] x [c1..c2]
  // Prerequisite: data_ holds 2D prefix sums
  std::int64_t query_rect(std::size_t r1, std::size_t c1, std::size_t r2, std::size_t c2) const noexcept {
    return data_[idx(r2 + 1, c2 + 1)]
         - data_[idx(r1,     c2 + 1)]
         - data_[idx(r2 + 1, c1)]
         + data_[idx(r1,     c1)];
  }
};
Production Pitfall: 32-Bit Signed Overflow & Non-Invertible Operations

Even when input array elements fit comfortably inside 32-bit signed integers ($|A[i]| \le 10^9$), a prefix sum over $N = 10^5$ elements reaches $10^{14}$, overflowing int32_t (max $\approx 2.14 \times 10^9$) and invoking Undefined Behavior in C/C++. Always widen prefix sum accumulators to int64_t. Additionally, remember that prefix queries require an invertible binary operation (an abelian group like $+$ with inverse $-$, or $\oplus$ with self-inverse $\oplus$). You cannot use prefix arrays for range minimum/maximum or $\gcd$ queries because $\min$ and $\gcd$ lack inverse elements—use a Sparse Table or Segment Tree instead.

Check yourself Question 1 of 4
Concept Check 2D Prefix Sum Inclusion-Exclusion Formula recall

Given a 1-indexed 2D prefix sum table `P` of size $(R+1) \times (C+1)$ where `P[r][c]` stores the sum of `A[0..r-1][0..c-1]`, you need to compute the sum of the 0-indexed inclusive subgrid `[r1 .. r2] x [c1 .. c2]` in $O(1)$ time.

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why is it mathematically impossible to answer arbitrary interior range `[L, R]` queries ($L > 0$) from a 1D prefix table alone for `min`, `max`, `gcd`, or bitwise `OR`?

Key Takeaways

Always allocate 1D prefix sums with size N + 1 and sentinel P[0] = 0 (and 2D prefix sums with (R + 1) x (C + 1)) to eliminate boundary branches during range queries.
A difference array turns O(N) range additions on [L, R] into two O(1) point updates: D[L] += delta and D[R + 1] -= delta.
In 2D, subgrid range additions update 4 corners of the 2D difference array in O(1), and subgrid sum queries combine 4 corners of the 2D prefix table via Inclusion-Exclusion.
Prefix queries require an invertible group operation (like addition or XOR); idempotent operations like min, max, or GCD cannot be inverted across prefix boundaries.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue