LeetCopilot Logo
LeetCopilot
LeetCode Pattern/Hash Map/Prefix Sum + Hash Map: Solve Subarray Problems in O(n)

Prefix Sum + Hash Map: Solve Subarray Problems in O(n)

LeetCopilot Team
Dec 16, 2025
14 min read
Hash MapPrefix SumSubarrayInterview Prep
Learn how combining prefix sums with hash maps unlocks O(n) solutions for subarray problems with negative numbers. Master the pattern that sliding window can't handle.

You see a problem: "Find subarrays with sum equals k." You think: "Sliding window!"

But then you notice: the array has negative numbers. Your sliding window solution fails.

This is where prefix sum + hash map comes in—the pattern that handles what sliding window cannot.

This combination is one of the most powerful hash map techniques. It appears in 20+ LeetCode problems, from "Subarray Sum Equals K" to "Continuous Subarray Sum" to "Subarray Sum Divisible by K." But the pattern isn't intuitive—it requires understanding both prefix sums and complement search.

This guide will teach you:

  • Why prefix sum alone isn't enough
  • The mathematical insight that makes hash maps necessary
  • When to use this vs. sliding window
  • The universal template with critical initialization
  • Common mistakes (especially the {0: 1} trap)
  • Variations (divisibility, modulo, XOR)

Master this, and subarray sum problems with negative numbers become solvable.

Why Prefix Sum Alone Isn't Enough

The naive approach

Problem: Count subarrays with sum equals k.

Brute force:

python
count = 0
for i in range(len(nums)):
    current_sum = 0
    for j in range(i, len(nums)):
        current_sum += nums[j]
        if current_sum == k:
            count += 1
# O(n²) time

Why sliding window fails

You might think: "Use sliding window!"

python
# WRONG - doesn't work with negative numbers
left = 0
current_sum = 0
count = 0

for right in range(len(nums)):
    current_sum += nums[right]
    
    while current_sum > k:  # When to shrink?
        current_sum -= nums[left]
        left += 1
    
    if current_sum == k:
        count += 1

Why it fails: With negative numbers, shrinking doesn't guarantee the sum decreases. You lose the monotonicity property that sliding window requires.

Example:

text
nums = [1, -1, 1, 1], k = 2

At right=2: sum=1, should we shrink? 
If we shrink, we might miss valid subarrays!

For detailed comparison, see Sliding Window vs Prefix Sum.

Enter prefix sum

Prefix sum lets us compute any subarray sum in O(1):

python
# Build prefix sum
prefix = [0]
for num in nums:
    prefix.append(prefix[-1] + num)

# Sum from i to j
subarray_sum = prefix[j+1] - prefix[i]

But we still need to check all pairs (i, j) → O(n²).

This is where hash map comes in.

The Hash Map Addition: Store Prefix Sums

The mathematical insight

If prefix[j] - prefix[i] = k, then subarray from i+1 to j has sum k.

Rearrange: prefix[i] = prefix[j] - k

So for each position j:

  1. Compute prefix[j]
  2. Check if prefix[j] - k exists in our hash map
  3. If yes, we found subarray(s) with sum k

Key insight: Store prefix sums in a hash map for O(1) lookup!

Visual proof

text
nums = [1, 2, 3, 4], k = 6

Prefix sums: [0, 1, 3, 6, 10]

At j=3 (prefix=6):
  Looking for prefix[i] = 6 - 6 = 0
  Found at i=0!
  Subarray [1,2,3] has sum 6 ✓

At j=4 (prefix=10):
  Looking for prefix[i] = 10 - 6 = 4
  Not found (no subarray ending at index 3 with sum 6)

Why frequency map?

What if the same prefix sum appears multiple times?

text
nums = [1, -1, 1, -1], k = 0

Prefix sums: [0, 1, 0, 1, 0]
                ↑     ↑     ↑
            Three times prefix=0!

Each occurrence of prefix[j] - k represents a valid subarray.

Solution: Store frequency of each prefix sum.

The Universal Template

Python Template

python
def subarraySum(nums: List[int], k: int) -> int:
    """
    Count subarrays with sum equals k.
    
    Time: O(n)
    Space: O(n)
    """
    count = 0
    prefix_sum = 0
    sum_count = {0: 1}  # CRITICAL: Initialize with {0: 1}
    
    for num in nums:
        prefix_sum += num
        
        # Check if (prefix_sum - k) exists
        if prefix_sum - k in sum_count:
            count += sum_count[prefix_sum - k]
        
        # Add current prefix sum to map
        sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
    
    return count

Critical points:

  1. Initialize with {0: 1}: Handles subarrays starting from index 0
  2. Check before adding: Count subarrays ending at current position
  3. Store frequency: Handle multiple occurrences of same prefix sum

Java Template

java
public int subarraySum(int[] nums, int k) {
    int count = 0;
    int prefixSum = 0;
    Map<Integer, Integer> sumCount = new HashMap<>();
    sumCount.put(0, 1);  // Critical initialization
    
    for (int num : nums) {
        prefixSum += num;
        
        if (sumCount.containsKey(prefixSum - k)) {
            count += sumCount.get(prefixSum - k);
        }
        
        sumCount.put(prefixSum, sumCount.getOrDefault(prefixSum, 0) + 1);
    }
    
    return count;
}

JavaScript Template

javascript
function subarraySum(nums, k) {
  let count = 0;
  let prefixSum = 0;
  const sumCount = new Map([[0, 1]]);  // Critical initialization
  
  for (const num of nums) {
    prefixSum += num;
    
    if (sumCount.has(prefixSum - k)) {
      count += sumCount.get(prefixSum - k);
    }
    
    sumCount.set(prefixSum, (sumCount.get(prefixSum) || 0) + 1);
  }
  
  return count;
}

The {0: 1} Initialization Trap

This is the #1 mistake people make with this pattern.

Why {0: 1} is critical

Without initialization:

python
nums = [1, 2, 3], k = 3

sum_count = {}  # Wrong!

i=0: prefix=1, check 1-3=-2 (not found), add {1: 1}
i=1: prefix=3, check 3-3=0 (not found!), add {1: 1, 3: 1}
i=2: prefix=6, check 6-3=3 (found!), count=1

Result: 1 (WRONG - should be 2, missing [1,2] and [3])

With initialization:

python
nums = [1, 2, 3], k = 3

sum_count = {0: 1}  # Correct!

i=0: prefix=1, check 1-3=-2 (not found), add {0: 1, 1: 1}
i=1: prefix=3, check 3-3=0 (found!), count=1, add {0: 1, 1: 1, 3: 1}
i=2: prefix=6, check 6-3=3 (found!), count=2, add {0: 1, 1: 1, 3: 1, 6: 1}

Result: 2(found [1,2] and [3])

What {0: 1} means

Conceptual meaning: "There's one way to get sum 0: take nothing (empty prefix)."

Practical effect: Handles subarrays that start from index 0.

When prefix[j] = k, we need prefix[i] = 0 (i.e., subarray from 0 to j).

For more debugging tips, see Subarray Sum Mistakes.

Pattern 1: Subarray Sum Equals K

Problem: Subarray Sum Equals K (LeetCode 560)

Problem: Count subarrays with sum equals k.

Full solution with trace:

python
def subarraySum(nums: List[int], k: int) -> int:
    count = 0
    prefix_sum = 0
    sum_count = {0: 1}
    
    for num in nums:
        prefix_sum += num
        
        if prefix_sum - k in sum_count:
            count += sum_count[prefix_sum - k]
        
        sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
    
    return count

Trace example:

text
nums = [1, 2, 3], k = 3

Step   num  prefix  check           sum_count                  count
-----  ---  ------  --------------  -------------------------  -----
init   -    0       -               {0: 1}                     0
0      1    1       1-3=-2 (no)     {0: 1, 1: 1}               0
1      2    3       3-3=0 (yes!)    {0: 1, 1: 1, 3: 1}         1
2      3    6       6-3=3 (yes!)    {0: 1, 1: 1, 3: 1, 6: 1}   2

Found 2 subarrays: [1,2] and [3]

Complexity: O(n) time, O(n) space

Pattern 2: Continuous Subarray Sum (Divisibility)

Problem: Continuous Subarray Sum (LeetCode 523)

Problem: Check if array has subarray (size ≥ 2) with sum divisible by k.

Key insight: If two prefix sums have the same remainder when divided by k, the subarray between them is divisible by k.

python
def checkSubarraySum(nums: List[int], k: int) -> bool:
    """
    Use modulo of prefix sums.
    
    Time: O(n)
    Space: O(k)
    """
    # Key: remainder, Value: earliest index
    remainder_index = {0: -1}  # Handle subarray from start
    prefix_sum = 0
    
    for i, num in enumerate(nums):
        prefix_sum += num
        remainder = prefix_sum % k
        
        if remainder in remainder_index:
            # Check if subarray length >= 2
            if i - remainder_index[remainder] >= 2:
                return True
        else:
            # Store earliest index for this remainder
            remainder_index[remainder] = i
    
    return False

Why it works:

text
If prefix[j] % k == prefix[i] % k, then:
  (prefix[j] - prefix[i]) % k == 0
  → subarray from i+1 to j is divisible by k

Example:

text
nums = [23, 2, 4, 6, 7], k = 6

Prefix: [0, 23, 25, 29, 35, 42]
Modulo: [0,  5,  1,  5,  5,  0]
             ↑        ↑   ↑
        Same remainder 5!

At i=3: remainder=5, found at i=1, distance=2 ✓
Subarray [2,4,6] has sum 12, divisible by 6

Pattern 3: Subarray Sum Divisible by K

Problem: Subarray Sum Divisible by K (LeetCode 974)

Problem: Count subarrays with sum divisible by k.

python
def subarraysDivByK(nums: List[int], k: int) -> int:
    """
    Count subarrays with sum divisible by k.
    
    Time: O(n)
    Space: O(k)
    """
    count = 0
    prefix_sum = 0
    remainder_count = {0: 1}  # Critical initialization
    
    for num in nums:
        prefix_sum += num
        remainder = prefix_sum % k
        
        # Handle negative remainders
        if remainder < 0:
            remainder += k
        
        if remainder in remainder_count:
            count += remainder_count[remainder]
        
        remainder_count[remainder] = remainder_count.get(remainder, 0) + 1
    
    return count

Key difference from #523: Count all subarrays (not just check existence).

Handling negative remainders:

python
# Python's % can return negative
-5 % 3 = 1 (in Python)  # But we want positive remainder

# Fix: add k if negative
remainder = prefix_sum % k
if remainder < 0:
    remainder += k

Pattern 4: Prefix XOR (Variation)

The same pattern works with XOR instead of sum.

Problem: XOR Queries of a Subarray (LeetCode 1310)

python
def xorQueries(arr: List[int], queries: List[List[int]]) -> List[int]:
    """
    Prefix XOR for range queries.
    
    Time: O(n + q)
    Space: O(n)
    """
    # Build prefix XOR
    prefix_xor = [0]
    for num in arr:
        prefix_xor.append(prefix_xor[-1] ^ num)
    
    # Answer queries
    result = []
    for left, right in queries:
        # XOR property: a ^ a = 0
        result.append(prefix_xor[right + 1] ^ prefix_xor[left])
    
    return result

Why it works: XOR has the property a ^ a = 0, similar to subtraction.

When to Use This vs. Sliding Window

Decision framework

code
Does the array have negative numbers?
├─ Yes → Use prefix sum + hash map
└─ No → Can you use sliding window?
    ├─ Finding subarrays with exact sum → Prefix sum + hash map
    ├─ Finding longest/shortest with condition → Sliding window
    └─ Counting subarrays → Prefix sum + hash map

Comparison table

Problem TypeNegative Numbers?Pattern
Subarray sum = kYesPrefix sum + hash map
Subarray sum = kNoPrefix sum + hash map (still works)
Longest subarray sum ≤ kNoSliding window
Longest subarray sum ≤ kYesCan't use sliding window easily
Count subarrays sum = kAnyPrefix sum + hash map

Rule of thumb: If you need exact sum or have negative numbers, use prefix sum + hash map.

Common Mistakes

Mistake 1: Forgetting {0: 1} Initialization

Wrong:

python
sum_count = {}  # Missing {0: 1}

Correct:

python
sum_count = {0: 1}

Mistake 2: Wrong Order of Operations

Wrong (update before checking):

python
sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
if prefix_sum - k in sum_count:
    count += sum_count[prefix_sum - k]

Correct (check before updating):

python
if prefix_sum - k in sum_count:
    count += sum_count[prefix_sum - k]
sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1

Mistake 3: Not Handling Negative Remainders

Wrong (in languages where % can be negative):

python
remainder = prefix_sum % k
# In Python, -5 % 3 = 1, but in Java/C++, -5 % 3 = -2

Correct:

python
remainder = prefix_sum % k
if remainder < 0:
    remainder += k

Mistake 4: Using Sliding Window for Negative Numbers

Wrong:

python
# Trying to use sliding window with negative numbers
# Doesn't work - monotonicity is broken

Correct:

python
# Use prefix sum + hash map instead

Complexity Analysis

Time Complexity

Building and querying: O(n)

  • Single pass through array
  • Each hash map operation is O(1) average

Space Complexity

Hash map size: O(n) worst case

  • Store up to n distinct prefix sums
  • For modulo problems: O(k) where k is the modulus

Optimization: For modulo problems, space is bounded by k.

Practice Problems

Beginner

  1. Subarray Sum Equals K (#560) - Classic pattern
  2. Continuous Subarray Sum (#523) - Divisibility check
  3. Subarray Sum Divisible by K (#974) - Count divisible

Intermediate

  1. Contiguous Array (#525) - Convert to sum problem
  2. Find Pivot Index (#724) - Prefix sum application
  3. Product of Array Except Self (#238) - Prefix product

Advanced

  1. Maximum Size Subarray Sum Equals k (#325) - Find longest
  2. Subarray Sums Divisible by K (#974) - Handle negatives
  3. Count Number of Nice Subarrays (#1248) - Transform to prefix sum

Summary

Prefix sum + hash map is the pattern for subarray sum problems with negative numbers.

Key principles:

  • Mathematical insight: prefix[i] = prefix[j] - k
  • Store frequencies: Handle multiple occurrences
  • Initialize {0: 1}: Handle subarrays from index 0
  • Check before adding: Count subarrays ending at current position

The template:

python
count = 0
prefix_sum = 0
sum_count = {0: 1}  # Critical!

for num in nums:
    prefix_sum += num
    if prefix_sum - k in sum_count:
        count += sum_count[prefix_sum - k]
    sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1

return count

When to use:

  • ✅ Subarray sum with negative numbers
  • ✅ Exact sum equals k
  • ✅ Divisibility conditions
  • ✅ Count subarrays

When NOT to use:

  • ❌ Longest/shortest with condition (use sliding window)
  • ❌ No negative numbers and simple condition (sliding window may be simpler)

Common mistakes:

  • ❌ Forgetting {0: 1}
  • ❌ Wrong order (update before check)
  • ❌ Not handling negative remainders
  • ❌ Using sliding window for negatives

Next steps:

  1. Master the complete hash map guide
  2. Learn when NOT to use hash maps
  3. Study subarray sum mistakes
  4. Practice the beginner problem set

Master this pattern, and subarray sum problems with negative numbers become solvable.

Want to Practice LeetCode Smarter?

LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.

Also compatible with Edge, Brave, and Opera

Related Tutorials