Skip to content
DSA sliding window 8 min read

Prefix Sum Applications: Subarray Sum Equals K & 2D Prefix Sums

Once you understand prefix sums, two powerful patterns open up: counting subarrays with a target sum in O(n) using a hash map, and answering any submatrix sum in O(1) with a 2D prefix sum. Both reuse the same “the answer is a difference of two prefixes” insight.

Subarray sum equals k

Count the number of contiguous subarrays whose elements sum to exactly k. Brute force is O(n²). The trick: a subarray a[l..r] sums to k exactly when prefix[r+1] - prefix[l] == k, i.e. prefix[l] == prefix[r+1] - k. So as we sweep, we keep a hash map of how many times each prefix sum has occurred, and for each new prefix we look up how many earlier prefixes equal current - k.

#include <vector>
#include <unordered_map>
int subarraySum(const std::vector<int>& a, int k) {
    std::unordered_map<long long, int> seen; // prefix value -> count
    seen[0] = 1;                              // empty prefix
    long long sum = 0;
    int count = 0;
    for (int x : a) {
        sum += x;
        auto it = seen.find(sum - k);
        if (it != seen.end()) count += it->second;
        seen[sum]++;
    }
    return count;
}

For a = [1, 1, 1], k = 2, the answer is 2 ([1,1] at indices 0-1 and 1-2). O(n) time, O(n) space.

For beginners: Seeding the map with {0: 1} represents the empty prefix — it’s what makes subarrays that start at index 0 count correctly. Forgetting it is the single most common bug here.

Going deeper: This hash-map-of-prefixes idea generalizes far beyond sums: prefix-XOR counts subarrays with a target XOR, and prefix-sum-mod-k counts subarrays divisible by k. Same skeleton, different key.

2D prefix sums (submatrix sum in O(1))

For a grid, build a 2D prefix array P where P[i][j] is the sum of the rectangle from the top-left corner (0,0) to (i-1, j-1). With a one-row and one-column border of zeros, every cell is:

P[i][j] = grid[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1]

We subtract P[i-1][j-1] because the overlapping top-left block got added twice (inclusion-exclusion). Then the sum of the submatrix with rows r1..r2 and columns c1..c2 (inclusive) is:

sum = P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]
#include <vector>
struct Matrix2D {
    std::vector<std::vector<long long>> P;
    Matrix2D(const std::vector<std::vector<int>>& g) {
        int m = g.size(), n = g[0].size();
        P.assign(m + 1, std::vector<long long>(n + 1, 0));
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                P[i+1][j+1] = g[i][j] + P[i][j+1] + P[i+1][j] - P[i][j];
    }
    long long query(int r1, int c1, int r2, int c2) { // inclusive
        return P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1];
    }
};
  • Build: O(m·n) time and space.
  • Each submatrix query: O(1).

For beginners: The four-term query formula is just inclusion-exclusion: take the big rectangle, subtract the strip above and the strip to the left, then add back the top-left corner you subtracted twice. Draw it once on paper and it sticks.

Common pitfalls

  • Skipping the {0: 1} seed in subarray-sum-equals-k — subarrays starting at index 0 get miscounted.
  • Updating the map before the lookup. Look up sum - k first, then record sum, or a zero-length match can sneak in.
  • 2D border mistakes. The padded prefix array is (m+1) x (n+1); query indices are shifted by one. Mixing inclusive/exclusive bounds is the usual source of bugs.
  • Overflow on large grids or values — use 64-bit accumulators.

Practice these on the exercises, or revisit the foundation in prefix sums.

Last updated June 25, 2026
Was this helpful?