The Quest Begins (The “Why”)
I was stuck on a LeetCode problem that felt like trying to find a needle in a haystack while blindfolded. The prompt: “Given an array of integers and a target k, return the total number of continuous subarrays whose sum equals k.” My first instinct? Grab two loops, compute every possible sum, and count the matches. It worked on the tiny examples, but as soon as the test data grew to 10⁵ elements, my solution timed out like a boss fight where you keep missing the pattern.
I stared at the screen, frustrated, wondering why the “obvious” solution felt so… sluggish. I kept thinking there had to be a trick, a hidden rhythm I wasn’t seeing. That’s when I remembered a conversation with a senior dev who said, “Top coders don’t just write code; they spot patterns that turn exponential nightmares into linear strolls.”
So I embarked on a little detective work, channeling my inner Sherlock Holmes: look for the clue that repeats, the signal that hides in plain sight.
The Revelation (The Insight)
The clue was prefix sums.
If you compute the cumulative sum up to each index, you get an array prefix[i] = sum of nums[0..i]. Now, the sum of a subarray nums[l..r] is simply prefix[r] - prefix[l-1] (with prefix[-1] treated as 0).
We want prefix[r] - prefix[l-1] = k. Rearranging: prefix[l-1] = prefix[r] - k.
So for each current prefix sum, we just need to know how many times we’ve already seen the value prefix[current] - k. If we’ve seen it c times, then there are c subarrays ending at the current index that sum to k.
That’s it! The problem collapses to a single pass with a hash map that stores how many times each prefix sum has appeared. No nested loops, no O(n²) dread—just O(n) time and O(n) space.
The “aha!” moment hit when I realized I’d been counting subarrays by brute force while the answer was literally sitting in the running total I was already computing. It felt like Holmes snapping his fingers and saying, “The game is afoot, Watson!”
Wielding the Power (Code & Examples)
The Struggle – Naïve O(n²) Approach
function subarraySum(nums, k) {
let count = 0;
for (let i = 0; i < nums.length; i++) {
let sum = 0;
for (let j = i; j < nums.length; j++) {
sum += nums[j];
if (sum === k) count++;
}
}
return count;
}
What’s wrong?
- Two nested loops → O(n²).
- Works fine on tiny inputs, but the moment
nums.lengthhits 10⁴+, the runtime explodes. - Easy to miss the off‑by‑one trap when you try to optimize by breaking early; you might accidentally skip valid subarrays.
The Victory – Pattern‑Recognized O(n) Solution
function subarraySum(nums, k) {
const map = new Map(); // prefixSum -> frequency
map.set(0, 1); // empty prefix before we start
let prefix = 0;
let count = 0;
for (const num of nums) {
prefix += num; // current prefix sum
const needed = prefix - k; // we need a previous prefix equal to this
count += map.get(needed) || 0; // add how many times we've seen it
map.set(prefix, (map.get(prefix) || 0) + 1); // record current prefix
}
return count;
}
Why this works:
-
map.set(0, 1)handles subarrays that start at index 0. - Each iteration does O(1) work: a lookup, an addition, and an update.
- No more worrying about where a subarray begins or ends; the hash map does the bookkeeping.
Common traps to avoid:
- Forgetting to seed the map with
{0:1}. Without it, subarrays that begin at the first element are missed. - Updating the map before checking for
needed. That would count the current prefix as a previous one, leading to over‑counting. - Using a plain object instead of a
Mapwhen dealing with negative prefix sums—objects coerce keys to strings, which can still work but is less clear; aMapkeeps the intent obvious.
Quick Test
console.log(subarraySum([1,1,1], 2)); // 2 → [1,1] at indices 0‑1 and 1‑2
console.log(subarraySum([1,2,3], 3)); // 2 → [1,2] and [3]
console.log(subarraySum([1,-1,0], 0)); // 4 → various combos
The same logic passes all edge cases, runs in linear time, and feels like you’ve just unlocked a cheat code.
Why This New Power Matters
Spotting the prefix‑sum pattern isn’t just a party trick for interview questions—it’s a mindset shift. Once you train yourself to ask, “What cumulative property can I track?” you’ll start seeing it everywhere:
- Sliding window problems become trivial when you realize you only need the sum of the current window.
- Two‑sum and three‑sum variants reduce to hash‑map lookups after you sort or fix one element.
- Even string‑matching algorithms like Rabin‑Karp rely on rolling hashes, which are essentially prefix sums under a modulus.
When you internalize this pattern, you stop grinding through nested loops and start designing solutions that scale. It’s the difference between swinging a sword blindly and wielding a lightsaber that cuts through the noise.
Imagine you’re building a real‑time analytics dashboard that needs to count how many user‑action sequences hit a revenue threshold every second. The naïve approach would choke under load; the prefix‑sum hashmap solution stays snappy, keeping your users happy and your servers calm.
Your Turn – A Mini‑Quest
Here’s a challenge to flex your new pattern‑recognition muscle:
Given a string, find the length of the longest substring without repeating characters.
Think about what information you need to keep as you scan the string. What’s the “prefix” analogue for characters? How can a hash map help you jump past duplicates in O(n) time?
Give it a shot, tweet your solution, or drop a link in the comments. I’d love to see how you turn this insight into your own victory dance.
Happy coding, and may your pattern‑spotting be ever sharp! 🚀
Top comments (0)