Back to Arrays
Q61Question 61 of 109

Subarray Sum Equals K (Prefix Sum with a Hash Map)

Key Concepts
  • A subarray sum equals the difference of two prefix sums: prefix[i] - prefix[j] = k.
  • At each index, count how many earlier prefix sums equal prefix[i] - k using a hash map.
  • Seed the map with prefix value 0 mapped to count 1 (representing the empty prefix before the array).
  • Look up before inserting to avoid matching an element against itself.
  • This works with negatives and zeros where sliding windows fail.
  • Time is O(N) and space is O(N) for the hash map.

Definition

Count earlier prefix sums equal to the current prefix sum minus k, using a hash map of prefix-sum frequencies.
  • This problem counts contiguous subarrays summing to exactly k, in an array with possible negatives.
  • The prefix sum at index i is the sum of all elements from 0 to i.
  • A subarray from j+1 to i sums to k when prefix[i] - prefix[j] = k, i.e., prefix[j] = prefix[i] - k.
  • A hash map tracks how many times each prefix value has occurred.
  • Seed the map with {0: 1} for the empty prefix; look up before insert.
  • Time is O(N) and space is O(N).

At the National Bank of India, Rajesh walks through a sequence of daily net cash flows for a suspense account and counts how many continuous stretches netted exactly zero (a perfectly balanced stretch). He keeps a running total and remembers how often each total appeared. Whenever his current total minus the target has been seen before, each earlier occurrence marks the start of a qualifying stretch.

Understanding the Concepts

What is the Subarray Sum Equals K problem?

Given an array of integers (possibly including negatives) and a target value k, count the total number of contiguous subarrays whose elements sum exactly to k. This is LeetCode 560.

For example, given [1, 1, 1] and k = 2, the answer is 2: subarrays [1, 1] starting at index 0 and [1, 1] starting at index 1.

How does the prefix-sum hash map work?

Define the prefix sum at index i as the sum of all elements from 0 through i. The sum of any subarray from j+1 to i equals prefix[i] - prefix[j]. This subarray sums to k when prefix[j] = prefix[i] - k.

So as we scan left to right maintaining a running prefix, the number of subarrays ending at the current index with sum k equals the number of times prefix - k has appeared as an earlier prefix sum.

JAVA
package com.bank.service;

import java.util.HashMap;
import java.util.Map;

public class SubarraySumKService {

    /**
     * Counts continuous stretches of cash flows summing to exactly target.
     * Used by the National Bank of India reconciliation team.
     */
    public static int subarraySum(int[] flows, int k) {
        Map<Long, Integer> prefixCount = new HashMap<>();
        prefixCount.put(0L, 1); // Seed: empty prefix

        long prefix = 0;
        int count = 0;

        for (int flow : flows) {
            prefix += flow;
            // Look up before insert
            count += prefixCount.getOrDefault(prefix - k, 0);
            prefixCount.merge(prefix, 1, Integer::sum);
        }
        return count;
    }

    public static void main(String[] args) {
        int[] flows = {10000, -5000, 5000, -10000, 10000};
        int target = 10000;
        System.out.println("Stretches summing to Rs." + target
            + ": " + subarraySum(flows, target));
    }
}
BASH
javac -d out com/bank/service/SubarraySumKService.java
java -cp out com.bank.service.SubarraySumKService
TEXT
Stretches summing to Rs.10000: 4

Why is the seed {0: 1} so important?

The seed represents the empty prefix before any element. Without it, a subarray starting at index 0 that sums to k would not be counted, because the lookup for prefix - k = 0 would find nothing. This is the classic off-by-one trap.

JAVA
// WRONG: no seed
Map<Long, Integer> map = new HashMap<>(); // Missing seed
// A subarray [0..i] summing to k is missed because prefix-k=0 is not found

// CORRECT: seed with {0: 1}
Map<Long, Integer> map = new HashMap<>();
map.put(0L, 1); // Empty prefix counts as one occurrence

Why look up before inserting?

We look up prefix - k before recording the current prefix in the map. If we inserted first, we might match the current element against itself, counting a zero-length subarray.

Why does the sliding window fail here?

Sliding windows rely on the monotonicity of the running sum: adding elements increases the sum and removing elements decreases it. With negatives, adding an element might decrease the sum and removing one might increase it, breaking the invariant. The prefix-sum-with-hash-map method handles the general signed case.

What are the edge cases?

Several edge cases require attention.

  • k = 0: counts subarrays summing to zero, which can exist even in positive arrays if they contain canceling elements.
  • All zeros: every subarray sums to zero, giving N*(N+1)/2 subarrays when k = 0.
  • Negative target: fully supported by the prefix difference.
  • Single element equal to k: counted via the seed.
JAVA
package com.bank.core;

public class SubarraySumEdgeCases {
    public static void main(String[] args) {
        // k = 0 with canceling elements
        System.out.println(subarraySum(new int[]{1, -1, 1, -1}, 0)); // 4

        // Single element equals k
        System.out.println(subarraySum(new int[]{5000}, 5000)); // 1

        // Negative target
        System.out.println(subarraySum(new int[]{-1, -2, 3}, -3)); // 1
    }

    static int subarraySum(int[] arr, int k) {
        java.util.Map<Long, Integer> map = new java.util.HashMap<>();
        map.put(0L, 1); long prefix = 0; int count = 0;
        for (int a : arr) { prefix += a; count += map.getOrDefault(prefix - k, 0); map.merge(prefix, 1, Integer::sum); }
        return count;
    }
}
TEXT
4
1
1

What is the time and space complexity?

Time is O(N): one pass with O(1) amortized map operations per element. Space is O(N) for the hash map in the worst case (all distinct prefix sums).

This is the canonical prefix-sum counting pattern. Variations include:

| Problem | What to track in the map |
|---|---|
| Subarray sum = k | Prefix sum frequency |
| Subarray sum divisible by k | Prefix sum mod k frequency |
| Longest subarray sum = k | First occurrence of each prefix |
| Equal zeros and ones | First occurrence of transformed prefix |

What should a senior engineer know?

A senior engineer should understand these deeper aspects.

  • Use long for prefix sums: integer overflow is possible on long financial sequences.
  • The seed and ordering are the two correctness linchpins: getting either wrong produces subtle bugs.
  • When to use this vs sliding window: the moment negatives appear, reach for prefix-sum-with-map. Sliding windows are for positive-only arrays.
  • HashMap vs array: if the prefix range is bounded, an array-based frequency map is faster.

Visual Overview

DIAGRAM
  Flows: [10000, -5000, 5000, -10000, 10000], k=10000

  ┌───────────────────────────────────────────────┐
  │  Seed: map = {0: 1}                           │
  │                                                │
  │  i=0: prefix=10000                             │
  │    lookup 10000-10000=0 -> found 1, count=1    │
  │    map = {0:1, 10000:1}                        │
  │                                                │
  │  i=1: prefix=5000                              │
  │    lookup 5000-10000=-5000 -> 0, count=1       │
  │    map = {0:1, 10000:1, 5000:1}                │
  │                                                │
  │  i=2: prefix=10000                             │
  │    lookup 10000-10000=0 -> found 1, count=2    │
  │    map = {0:1, 10000:2, 5000:1}                │
  │                                                │
  │  i=3: prefix=0                                 │
  │    lookup 0-10000=-10000 -> 0, count=2         │
  │    map = {0:2, 10000:2, 5000:1}                │
  │                                                │
  │  i=4: prefix=10000                             │
  │    lookup 10000-10000=0 -> found 2, count=4    │
  │    map = {0:2, 10000:3, 5000:1}                │
  └───────────────────────────────────────────────┘

  Result: 4 subarrays sum to 10000

Key Takeaway

This problem counts contiguous subarrays summing exactly to k in an array that may contain negatives, and you solve it in linear time using a running prefix sum and a hash map of prefix-sum frequencies. A subarray sum equals the difference of two prefix sums, so a subarray ending at the current index sums to k when some earlier prefix equals the current prefix minus k. At each index, you add to your answer the count of times that value has appeared, then record the current prefix.

The two correctness linchpins are the seed and the ordering. Seed the map with prefix value 0 at count 1 to represent the empty prefix, so subarrays starting at index 0 are counted. Look up before inserting so you never match an element against itself.

At the National Bank of India, Rajesh used this on the reconciliation team. They reviewed daily net cash flows for a suspense account and counted how many continuous stretches netted exactly a target amount, usually zero, because zero-netting stretches meant the account self-reconciled over that period. Flows could be positive or negative, so a sliding window was not applicable.

The senior-level insight is that the prefix-sum-with-hash-map technique is specifically the tool for signed arrays where sliding windows break. Knowing to reach for it the moment negatives appear is the real signal of experience.

Remember one sentence: at each index, look up prefix minus k in a frequency map of earlier prefix sums, seeded with {0: 1}, and the total lookups give the count of subarrays summing to k.

---

Share
Use ← → arrow keys to navigate