4.8 KiB
Subarray Sum — Pattern Recognition Guide
- Core Trigger: Subarray + Sum
- The Multiplicative Variant: Subarray + Product
- The Counter-Pattern: When Is It Not Prefix Sums?
- Generalizing the Concept: "Prefix State"
- Summary Checklist
Core Trigger: Subarray + Sum
When you see "subarray" and "sum" in the same problem description, Prefix Sums should almost always be your immediate first thought.
The fundamental formula:
Sum(i..j) = PrefixSum[j] - PrefixSum[i-1]
This algebraic rewrite turns a range query into a difference between two points. It completely eliminates the need to re-scan the array.
The mental trigger is simple: subarray + sum → prefix sums. Always.
Variations of this pattern:
- "Count of subarrays where sum equals K": Use the hash map approach, looking for `current_sum - K`.
- "Longest/Shortest subarray where sum equals K": Instead of storing the frequency of the prefix sum in your map, store its first seen index (`map[prefix_sum] = index`). This lets you track length via `current_index - map[current_sum - K]`.
- "Subarray sum divisible by K": Store `prefix_sum % K` in your hash map instead of the raw sum.
The Multiplicative Variant: Subarray + Product
If the problem asks for a subarray product (e.g., "number of subarrays where product equals K"), the prefix sum pattern translates directly into a Prefix Product:
Product(i..j) = PrefixProduct[j] / PrefixProduct[i-1]
Instead of subtracting, you divide.
Edge case: Zeros reset the product to 0. Division by zero breaks the pattern. Handle by:
- Segmentation — split the array at zeros, solve each segment independently
- Track position of last seen zero to reset boundaries
The Counter-Pattern: When Is It Not Prefix Sums?
While "subarray + sum" strongly hints at prefix sums, you need to look at the constraints and types of numbers to choose the absolute best tool.
| If you see "Subarray + Sum" AND… | The Real Pattern Is… | Why? |
|---|---|---|
| All numbers are strictly positive (>= 0) and you need to find a target sum or max length. | Sliding Window (Two Pointers) | Because the window sum is monotonic (expanding always increases the sum; shrinking always decreases it). Sliding window optimizes space to O(1) compared to O(n) for prefix sums. |
| Numbers can be negative, or you need to find an exact target sum. | Prefix Sum + Hash Map | Monotonicity is broken. Adding an element could make the sum smaller, so sliding window fails. You must use a hash map to remember past states. |
| The array is constantly being updated (dynamic updates) between sum queries. | Segment Tree or Fenwick Tree (BIT) | A standard prefix sum array takes O(n) to update if an element changes. Segment/Fenwick trees drop update and query times to O(log n). |
Generalizing the Concept: "Prefix State"
Don't limit this trick just to addition. The core philosophy of a prefix sum is accumulating history as you traverse linearly. You can use a hash map to track the "prefix state" for things that don't look like math at first.
| Problem | Mapping | Reduces To |
|---|---|---|
| Equal 0s and 1s | 0 → -1, 1 → +1 | Subarray sum = 0 |
| Equal odd/even | even → +1, odd → -1 | Subarray sum = 0 |
| Equal vowels/consonants | vowel → +1, consonant → -1 | Subarray sum = 0 |
| Equal A/B/C counts | Track (c_A - c_B, c_B - c_C) | Prefix state tuple repeats |
| Subarray product | Prefix product | Division (handle zeros) |
| Subarray XOR | Prefix XOR | XOR is invertible |
| Subarray sum | Prefix sum | Subtraction |
The pattern:
- Define a state that accumulates as you traverse
- Find a way to "undo" or compare states (subtract, XOR, divide, compare tuples)
- Use a hash map to find when the same state (or target difference) appears
Summary Checklist
- Subarray + Sum + Negative Numbers → Prefix Sum + Hash Map
- Subarray + Sum + Only Positive Numbers → Sliding Window
- Subarray + Sum + Frequent Updates → Segment/Fenwick Tree