Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window
MediumLC 560

Subarray Sum Equals K (LeetCode 560)

Target Frequency:MetaAmazonGoogle

You have an array of integers nums and a target integer k. Count how many contiguous, non-empty subarrays of nums add up to exactly k, and return that count.

Worked Examples

Example 1
Input:nums = [1,1,1], k = 2
Output:2
Explanation: [1,1] at positions 0-1 and [1,1] at positions 1-2 both sum to 2.
Example 2
Input:nums = [1,2,3], k = 3
Output:2
Explanation: [1,2] and [3] both sum to 3.

⚖️Formal Constraints & Bounds

  • 1 <= nums.length <= 2 * 104

  • -1000 <= nums[i] <= 1000

  • -107 <= k <= 107

Deep-Dive & Conceptual Insights

Core Insight

prefix[j] - prefix[i] = k implies prefix[i] = prefix[j] - k. By caching seen prefix sum frequencies in a hash table (Key: The running sum. Value: The number of times we've seen it.) initialized with {0: 1}, we resolve all valid left boundaries in single-pass O(N) time.

Real-World Scenario & Production Applications

Financial ledger auditing and telemetry delta balancing where transaction deltas can be positive or negative.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Prefix Sum & Hash Map Invariant

Maintain running prefix sum curr_sum and a hash map prefix_counts = {0: 1} tracking frequencies of previously seen prefix sums (Key: The running sum. Value: The number of times we've seen it.).

Mathematical Recurrence / Code Invariant
prefix_counts = {0: 1}  # Base state: empty prefix sum = 0
# Key: The running sum.
# Value: The number of times we've seen it.
curr_sum = 0
count = 0

Step-by-Step Execution Trace Table

Index inums[i]Running Sum (curr)Query Needed (curr - k)Matches in MapUpdated Total CountHash Map State {sum: freq}
Init—0——0{0: 1}
0111 - 2 = -100{0: 1, 1: 1}
1-100 - 2 = -200{0: 2, 1: 1}
2111 - 2 = -100{0: 2, 1: 2}
3122 - 2 = 02 ([0,3], [2,3])0 + 2 = 2{0: 2, 1: 2, 2: 1}
4133 - 2 = 12 ([1,4], [3,4])2 + 2 = 4{0: 2, 1: 2, 2: 1, 3: 1}
Worked on Paper

The Math, Step by Step

1. The numbers the code compares

Call P[i]P[i]P[i] the prefix sum at iii: the total of the first iii elements of nums. So P[0]=0P[0] = 0P[0]=0, because it adds no elements. The subarray from index LLL to index RRR has this total:

sum(L…R)=P[R+1]−P[L]\text{sum}(L \ldots R) = P[R + 1] - P[L]sum(L…R)=P[R+1]−P[L]

The code walks through nums once. After it adds nums[R], the variable running_sum holds P[R+1]P[R + 1]P[R+1]. The question for this step is: how many subarrays end at index RRR and have total k?

2. Turning "total equals k" into a lookup

A subarray from LLL to RRR has total k exactly when

P[R+1]−P[L]=k⟺P[L]=P[R+1]−kP[R + 1] - P[L] = k \quad\Longleftrightarrow\quad P[L] = P[R + 1] - kP[R+1]−P[L]=k⟺P[L]=P[R+1]−k

The right side is known at step RRR. The code calls it needed_prefix = running_sum - k. So the number of subarrays that end at RRR with total k equals the number of earlier prefix sums P[L]P[L]P[L], with L≤RL \le RL≤R, that are equal to needed_prefix.

The code keeps those earlier prefix sums in a hash map, prefix_counts. prefix_counts[s] is how many of P[0],…,P[R]P[0], \ldots, P[R]P[0],…,P[R] are equal to sss. One lookup, prefix_counts[needed_prefix], counts every matching start LLL at once.

3. Why this counts every subarray exactly once

Every subarray has exactly one end index RRR and one start index LLL. The code counts it at step RRR, when running_sum is P[R+1]P[R + 1]P[R+1] and the map holds P[L]P[L]P[L]. So each subarray is counted once, at its own end.

The rule P[L]=P[R+1]−kP[L] = P[R + 1] - kP[L]=P[R+1]−k comes from subtracting two totals. It does not need the values to be positive. That is why the code works with negative numbers in nums. A sliding window cannot do this: with negative values, a longer window can have a smaller total, so the window cannot decide when to shrink.

4. The two lines whose order matters

The base case prefix_counts[0] = 1. It stores P[0]=0P[0] = 0P[0]=0, the empty prefix, before the loop. Without it, subarrays that start at index 0 are never counted. In Example 2, nums = [1,2,3] and k = 3, the subarray [1,2] starts at index 0. Its total is found as P[2]−P[0]=3−0P[2] - P[0] = 3 - 0P[2]−P[0]=3−0. Without the stored P[0]P[0]P[0], the code returns 1 instead of 2.

Look up first, then store. The code checks needed_prefix before it runs prefix_counts[running_sum] += 1. So at step RRR the map holds only P[0],…,P[R]P[0], \ldots, P[R]P[0],…,P[R], and every match has L≤RL \le RL≤R. If the code stored P[R+1]P[R + 1]P[R+1] first, then k = 0 would match P[R+1]P[R + 1]P[R+1] with itself. That match is the empty subarray, which the problem does not count. For nums = [1] and k = 0, the store-first order returns 1, and the correct answer is 0.

5. Example 1 by hand

nums = [1,1,1], k = 2. Before the loop, prefix_counts = {0: 1}.

RRRnumrunning_sumneeded_prefixprefix_counts before the lookupAdded to total_matchestotal_matches
0111−2=−11 - 2 = -11−2=−1{0: 1}00
1122−2=02 - 2 = 02−2=0{0: 1, 1: 1}11
2133−2=13 - 2 = 13−2=1{0: 1, 1: 1, 2: 1}12

At R=1R = 1R=1, the match is P[0]=0P[0] = 0P[0]=0, so the subarray is indices 0 to 1. At R=2R = 2R=2, the match is P[1]=1P[1] = 1P[1]=1, so the subarray is indices 1 to 2. The code returns 2, the expected output.

6. Example 2 by hand

nums = [1,2,3], k = 3. Before the loop, prefix_counts = {0: 1}.

RRRnumrunning_sumneeded_prefixprefix_counts before the lookupAdded to total_matchestotal_matches
0111−3=−21 - 3 = -21−3=−2{0: 1}00
1233−3=03 - 3 = 03−3=0{0: 1, 1: 1}11
2366−3=36 - 3 = 36−3=3{0: 1, 1: 1, 3: 1}12

At R=1R = 1R=1, the match is P[0]=0P[0] = 0P[0]=0, which gives [1,2]. At R=2R = 2R=2, the match is P[2]=3P[2] = 3P[2]=3, which gives [3]. The code returns 2, the expected output.

7. From the formulas to the code
FormulaIn subarraySum
P[0]=0P[0] = 0P[0]=0 is stored onceprefix_counts[0] = 1
P[R+1]=P[R]+nums[R]P[R + 1] = P[R] + \text{nums}[R]P[R+1]=P[R]+nums[R]running_sum += num
P[L]=P[R+1]−kP[L] = P[R + 1] - kP[L]=P[R+1]−kneeded_prefix = running_sum - k
count of earlier P[L]P[L]P[L] equal to needed_prefixtotal_matches += prefix_counts[needed_prefix]
store P[R+1]P[R + 1]P[R+1] after the lookupprefix_counts[running_sum] += 1

Each step does one lookup and one store in the hash map, so the whole pass takes O(n)O(n)O(n) time. The map holds at most n+1n + 1n+1 different prefix sums, so it takes O(n)O(n)O(n) space.

Code Shape
Archetype Code Shape
Python
1prefix_counts = defaultdict(int)
2prefix_counts[0] = 1
3running_sum = 0
4total_matches = 0
5for num in nums:
6 running_sum += num
7 needed_prefix = running_sum - k
8 if needed_prefix in prefix_counts:
9 total_matches += prefix_counts[needed_prefix]
10 prefix_counts[running_sum] += 1
11return total_matches

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Negative numbers break the two-pointer sliding window from earlier archetypes entirely — growing the window can now decrease the sum, so "shrink when too big" no longer makes sense. This calls for a completely different mechanism: no pointers at all, just a running total and a memory of totals you've already seen.

🧾 The Analogy: A Running Bank Balance and a Ledger of Past Balances

Imagine tracking your running bank balance day by day, and writing down every balance you've ever had in a ledger, with how many times you've hit each one. Today's question: "was there ever a stretch of consecutive days where I gained exactly k?" That's true exactly when today's balance minus k matches some balance you had on an earlier day — because the days in between are precisely the stretch that gained k. You don't need to re-add up every possible stretch; you just check the ledger for "have I seen (today's balance) - k before?"

🗂️ What the Dictionary (prefix_counts) is Made Of

To make sure you never wonder what the dictionary stores:

  • Key: The running sum.
  • Value: The number of times we've seen it.
🔑 The Key Idea in the Code

The prefix_counts[0] = 1 seed handles the case where the winning stretch starts from day one — an implicit "balance of 0 before any days have passed."

  • Forgetting to seed the hash map with prefix_counts[0] = 1: If running_sum == k at index i, then needed_prefix = running_sum - k = 0. Without the initial prefix_counts[0] = 1 base case recording an implicit sum of 0 before index 0, subarrays starting at index 0 that sum to k are completely missed.

  • Attempting a two-pointer sliding window with negative numbers or zeroes: Sliding window requires monotonicity: expanding must increase (or stay non-decreasing) the sum, and shrinking must decrease it. When array elements can be negative, expanding can decrease the sum and shrinking can increase it, destroying the sliding window invariant. Prefix sum difference with a hash map is required.

  • Recording running_sum into prefix_counts before querying needed_prefix: If k == 0 and you insert running_sum into prefix_counts before checking if needed_prefix in prefix_counts, the element matches against its own just-inserted self, counting an invalid empty subarray of length 0.

  • Storing boolean presence in a set instead of frequencies in a dictionary: Because array elements can be zero or negative, the exact same prefix sum can occur multiple times at different indices. A set only remembers that a sum was seen, not how many times. Storing frequencies (Key: The running sum. Value: The number of times we've seen it.) is strictly necessary to count all valid combinations.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Continuous subarray sum condition with negative values or zeros where sliding window is non-monotonic.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: before num is added, prefix_counts holds how often each earlier prefix sum occurred, so the subarrays ending here with sum k number prefix_counts[running_sum - k].

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Using a hash set instead of a frequency map: several earlier prefixes can share the same sum (zeros, or values that cancel out), and each one starts a different subarray, so their counts must be added.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use a Prefix Sum Map. The sum of a subarray is the running sum at its end minus the running sum just before its start. So a subarray ending at the current element sums to k exactly when an earlier running sum equals running_sum minus k. I keep prefix_counts, which says how many times each running sum has appeared, look up running_sum minus k and add that count to the answer, and only then add the current running_sum to the map. Every subarray is counted once, at its last element, and nothing depends on the values being positive, so negative numbers work, where a sliding window would fail. The trap is the empty prefix: I must start with prefix_counts[0] = 1, or every subarray that begins at index 0 is missed; for [1, 1, 1] with k = 2, I would count 1 instead of 2. It's O(N) time and O(N) space for the map."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the loop: for num in nums runs N times; each pass does one addition to running_sum, one lookup of needed_prefix in prefix_counts and one insert, all O(1) on average. Total: O(N).

SPACE COMPLEXITY

O(N) Auxiliary

Look at memory allocations: Call stack uses O(1) Iterative. prefix_counts maps each prefix sum seen so far (the key) to how many times it has occurred (the value), holding at most N + 1 distinct keys in the worst case.

Formal Recurrence Relation

T(N) = ∑_{i=1}^N O(1) = O(N)

Derivation Progression

Prefix Sum Accumulation

prefix_sum += nums[i]

Computes running sum PiP_{i}Pi​ in O(1) arithmetic per element.

Hash Map Lookup for Target Difference

count += prefix_map[Pi - K]

Subarray nums[j..i] sums to K iff PiP_{i}Pi​ - Pj−1P_{j-1}Pj−1​ == K, meaning Pj−1P_{j-1}Pj−1​ == PiP_{i}Pi​ - K.

Map Insertion

prefix_map[Pi] += 1

Records current prefix sum in O(1) amortized hash map operation.

Variable Definitions

NNN

Total number of elements in the array

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative

🔵 Auxiliary Heap

O(N) Prefix sum frequency hash map

🟢 Output Space

O(1) Total count integer

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N)

Average Case

O(N)O(N)O(N)

Worst Case

O(N)O(N)O(N)

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "total number of subarrays whose sum equals k", "array may contain negative numbers". Because negative values destroy monotonicity of sliding windows, we utilize Cumulative Prefix Sums with Hash Map frequency counting.

CONSTRAINTS & BOUNDS

1≤N≤2×1041 \le N \le 2 \times 10^41≤N≤2×104, nums[i]∈[−1000,1000]nums[i] \in [-1000, 1000]nums[i]∈[−1000,1000]. O(N)O(N)O(N) time with hash map.

FAANG PRODUCTION TRAPS & EDGE CASES

Outside Python the prefix sums need a wide type: with N=2×104N = 2 \times 10^4N=2×104 values of size up to 100010001000, running_sum stays within 2×1072 \times 10^72×107 here, but the same code on larger inputs overflows 32 bits; use 64-bit sums.

Theory Context•Two Pointers & Sliding Window
MediumLC 560

Subarray Sum Equals K (LeetCode 560)

Target Frequency:MetaAmazonGoogle

You have an array of integers nums and a target integer k. Count how many contiguous, non-empty subarrays of nums add up to exactly k, and return that count.

Worked Examples

Example 1
Input:nums = [1,1,1], k = 2
Output:2
Explanation: [1,1] at positions 0-1 and [1,1] at positions 1-2 both sum to 2.
Example 2
Input:nums = [1,2,3], k = 3
Output:2
Explanation: [1,2] and [3] both sum to 3.

⚖️Formal Constraints & Bounds

  • 1 <= nums.length <= 2 * 104

  • -1000 <= nums[i] <= 1000

  • -107 <= k <= 107

Deep-Dive & Conceptual Insights

Core Insight

prefix[j] - prefix[i] = k implies prefix[i] = prefix[j] - k. By caching seen prefix sum frequencies in a hash table (Key: The running sum. Value: The number of times we've seen it.) initialized with {0: 1}, we resolve all valid left boundaries in single-pass O(N) time.

Real-World Scenario & Production Applications

Financial ledger auditing and telemetry delta balancing where transaction deltas can be positive or negative.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Prefix Sum & Hash Map Invariant

Maintain running prefix sum curr_sum and a hash map prefix_counts = {0: 1} tracking frequencies of previously seen prefix sums (Key: The running sum. Value: The number of times we've seen it.).

Mathematical Recurrence / Code Invariant
prefix_counts = {0: 1}  # Base state: empty prefix sum = 0
# Key: The running sum.
# Value: The number of times we've seen it.
curr_sum = 0
count = 0

Step-by-Step Execution Trace Table

Index inums[i]Running Sum (curr)Query Needed (curr - k)Matches in MapUpdated Total CountHash Map State {sum: freq}
Init—0——0{0: 1}
0111 - 2 = -100{0: 1, 1: 1}
1-100 - 2 = -200{0: 2, 1: 1}
2111 - 2 = -100{0: 2, 1: 2}
3122 - 2 = 02 ([0,3], [2,3])0 + 2 = 2{0: 2, 1: 2, 2: 1}
4133 - 2 = 12 ([1,4], [3,4])2 + 2 = 4{0: 2, 1: 2, 2: 1, 3: 1}
Worked on Paper

The Math, Step by Step

1. The numbers the code compares

Call P[i]P[i]P[i] the prefix sum at iii: the total of the first iii elements of nums. So P[0]=0P[0] = 0P[0]=0, because it adds no elements. The subarray from index LLL to index RRR has this total:

sum(L…R)=P[R+1]−P[L]\text{sum}(L \ldots R) = P[R + 1] - P[L]sum(L…R)=P[R+1]−P[L]

The code walks through nums once. After it adds nums[R], the variable running_sum holds P[R+1]P[R + 1]P[R+1]. The question for this step is: how many subarrays end at index RRR and have total k?

2. Turning "total equals k" into a lookup

A subarray from LLL to RRR has total k exactly when

P[R+1]−P[L]=k⟺P[L]=P[R+1]−kP[R + 1] - P[L] = k \quad\Longleftrightarrow\quad P[L] = P[R + 1] - kP[R+1]−P[L]=k⟺P[L]=P[R+1]−k

The right side is known at step RRR. The code calls it needed_prefix = running_sum - k. So the number of subarrays that end at RRR with total k equals the number of earlier prefix sums P[L]P[L]P[L], with L≤RL \le RL≤R, that are equal to needed_prefix.

The code keeps those earlier prefix sums in a hash map, prefix_counts. prefix_counts[s] is how many of P[0],…,P[R]P[0], \ldots, P[R]P[0],…,P[R] are equal to sss. One lookup, prefix_counts[needed_prefix], counts every matching start LLL at once.

3. Why this counts every subarray exactly once

Every subarray has exactly one end index RRR and one start index LLL. The code counts it at step RRR, when running_sum is P[R+1]P[R + 1]P[R+1] and the map holds P[L]P[L]P[L]. So each subarray is counted once, at its own end.

The rule P[L]=P[R+1]−kP[L] = P[R + 1] - kP[L]=P[R+1]−k comes from subtracting two totals. It does not need the values to be positive. That is why the code works with negative numbers in nums. A sliding window cannot do this: with negative values, a longer window can have a smaller total, so the window cannot decide when to shrink.

4. The two lines whose order matters

The base case prefix_counts[0] = 1. It stores P[0]=0P[0] = 0P[0]=0, the empty prefix, before the loop. Without it, subarrays that start at index 0 are never counted. In Example 2, nums = [1,2,3] and k = 3, the subarray [1,2] starts at index 0. Its total is found as P[2]−P[0]=3−0P[2] - P[0] = 3 - 0P[2]−P[0]=3−0. Without the stored P[0]P[0]P[0], the code returns 1 instead of 2.

Look up first, then store. The code checks needed_prefix before it runs prefix_counts[running_sum] += 1. So at step RRR the map holds only P[0],…,P[R]P[0], \ldots, P[R]P[0],…,P[R], and every match has L≤RL \le RL≤R. If the code stored P[R+1]P[R + 1]P[R+1] first, then k = 0 would match P[R+1]P[R + 1]P[R+1] with itself. That match is the empty subarray, which the problem does not count. For nums = [1] and k = 0, the store-first order returns 1, and the correct answer is 0.

5. Example 1 by hand

nums = [1,1,1], k = 2. Before the loop, prefix_counts = {0: 1}.

RRRnumrunning_sumneeded_prefixprefix_counts before the lookupAdded to total_matchestotal_matches
0111−2=−11 - 2 = -11−2=−1{0: 1}00
1122−2=02 - 2 = 02−2=0{0: 1, 1: 1}11
2133−2=13 - 2 = 13−2=1{0: 1, 1: 1, 2: 1}12

At R=1R = 1R=1, the match is P[0]=0P[0] = 0P[0]=0, so the subarray is indices 0 to 1. At R=2R = 2R=2, the match is P[1]=1P[1] = 1P[1]=1, so the subarray is indices 1 to 2. The code returns 2, the expected output.

6. Example 2 by hand

nums = [1,2,3], k = 3. Before the loop, prefix_counts = {0: 1}.

RRRnumrunning_sumneeded_prefixprefix_counts before the lookupAdded to total_matchestotal_matches
0111−3=−21 - 3 = -21−3=−2{0: 1}00
1233−3=03 - 3 = 03−3=0{0: 1, 1: 1}11
2366−3=36 - 3 = 36−3=3{0: 1, 1: 1, 3: 1}12

At R=1R = 1R=1, the match is P[0]=0P[0] = 0P[0]=0, which gives [1,2]. At R=2R = 2R=2, the match is P[2]=3P[2] = 3P[2]=3, which gives [3]. The code returns 2, the expected output.

7. From the formulas to the code
FormulaIn subarraySum
P[0]=0P[0] = 0P[0]=0 is stored onceprefix_counts[0] = 1
P[R+1]=P[R]+nums[R]P[R + 1] = P[R] + \text{nums}[R]P[R+1]=P[R]+nums[R]running_sum += num
P[L]=P[R+1]−kP[L] = P[R + 1] - kP[L]=P[R+1]−kneeded_prefix = running_sum - k
count of earlier P[L]P[L]P[L] equal to needed_prefixtotal_matches += prefix_counts[needed_prefix]
store P[R+1]P[R + 1]P[R+1] after the lookupprefix_counts[running_sum] += 1

Each step does one lookup and one store in the hash map, so the whole pass takes O(n)O(n)O(n) time. The map holds at most n+1n + 1n+1 different prefix sums, so it takes O(n)O(n)O(n) space.

Code Shape
Archetype Code Shape
Python
1prefix_counts = defaultdict(int)
2prefix_counts[0] = 1
3running_sum = 0
4total_matches = 0
5for num in nums:
6 running_sum += num
7 needed_prefix = running_sum - k
8 if needed_prefix in prefix_counts:
9 total_matches += prefix_counts[needed_prefix]
10 prefix_counts[running_sum] += 1
11return total_matches

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Negative numbers break the two-pointer sliding window from earlier archetypes entirely — growing the window can now decrease the sum, so "shrink when too big" no longer makes sense. This calls for a completely different mechanism: no pointers at all, just a running total and a memory of totals you've already seen.

🧾 The Analogy: A Running Bank Balance and a Ledger of Past Balances

Imagine tracking your running bank balance day by day, and writing down every balance you've ever had in a ledger, with how many times you've hit each one. Today's question: "was there ever a stretch of consecutive days where I gained exactly k?" That's true exactly when today's balance minus k matches some balance you had on an earlier day — because the days in between are precisely the stretch that gained k. You don't need to re-add up every possible stretch; you just check the ledger for "have I seen (today's balance) - k before?"

🗂️ What the Dictionary (prefix_counts) is Made Of

To make sure you never wonder what the dictionary stores:

  • Key: The running sum.
  • Value: The number of times we've seen it.
🔑 The Key Idea in the Code

The prefix_counts[0] = 1 seed handles the case where the winning stretch starts from day one — an implicit "balance of 0 before any days have passed."

  • Forgetting to seed the hash map with prefix_counts[0] = 1: If running_sum == k at index i, then needed_prefix = running_sum - k = 0. Without the initial prefix_counts[0] = 1 base case recording an implicit sum of 0 before index 0, subarrays starting at index 0 that sum to k are completely missed.

  • Attempting a two-pointer sliding window with negative numbers or zeroes: Sliding window requires monotonicity: expanding must increase (or stay non-decreasing) the sum, and shrinking must decrease it. When array elements can be negative, expanding can decrease the sum and shrinking can increase it, destroying the sliding window invariant. Prefix sum difference with a hash map is required.

  • Recording running_sum into prefix_counts before querying needed_prefix: If k == 0 and you insert running_sum into prefix_counts before checking if needed_prefix in prefix_counts, the element matches against its own just-inserted self, counting an invalid empty subarray of length 0.

  • Storing boolean presence in a set instead of frequencies in a dictionary: Because array elements can be zero or negative, the exact same prefix sum can occur multiple times at different indices. A set only remembers that a sum was seen, not how many times. Storing frequencies (Key: The running sum. Value: The number of times we've seen it.) is strictly necessary to count all valid combinations.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Continuous subarray sum condition with negative values or zeros where sliding window is non-monotonic.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: before num is added, prefix_counts holds how often each earlier prefix sum occurred, so the subarrays ending here with sum k number prefix_counts[running_sum - k].

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Using a hash set instead of a frequency map: several earlier prefixes can share the same sum (zeros, or values that cancel out), and each one starts a different subarray, so their counts must be added.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use a Prefix Sum Map. The sum of a subarray is the running sum at its end minus the running sum just before its start. So a subarray ending at the current element sums to k exactly when an earlier running sum equals running_sum minus k. I keep prefix_counts, which says how many times each running sum has appeared, look up running_sum minus k and add that count to the answer, and only then add the current running_sum to the map. Every subarray is counted once, at its last element, and nothing depends on the values being positive, so negative numbers work, where a sliding window would fail. The trap is the empty prefix: I must start with prefix_counts[0] = 1, or every subarray that begins at index 0 is missed; for [1, 1, 1] with k = 2, I would count 1 instead of 2. It's O(N) time and O(N) space for the map."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the loop: for num in nums runs N times; each pass does one addition to running_sum, one lookup of needed_prefix in prefix_counts and one insert, all O(1) on average. Total: O(N).

SPACE COMPLEXITY

O(N) Auxiliary

Look at memory allocations: Call stack uses O(1) Iterative. prefix_counts maps each prefix sum seen so far (the key) to how many times it has occurred (the value), holding at most N + 1 distinct keys in the worst case.

Formal Recurrence Relation

T(N) = ∑_{i=1}^N O(1) = O(N)

Derivation Progression

Prefix Sum Accumulation

prefix_sum += nums[i]

Computes running sum PiP_{i}Pi​ in O(1) arithmetic per element.

Hash Map Lookup for Target Difference

count += prefix_map[Pi - K]

Subarray nums[j..i] sums to K iff PiP_{i}Pi​ - Pj−1P_{j-1}Pj−1​ == K, meaning Pj−1P_{j-1}Pj−1​ == PiP_{i}Pi​ - K.

Map Insertion

prefix_map[Pi] += 1

Records current prefix sum in O(1) amortized hash map operation.

Variable Definitions

NNN

Total number of elements in the array

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative

🔵 Auxiliary Heap

O(N) Prefix sum frequency hash map

🟢 Output Space

O(1) Total count integer

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N)

Average Case

O(N)O(N)O(N)

Worst Case

O(N)O(N)O(N)

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "total number of subarrays whose sum equals k", "array may contain negative numbers". Because negative values destroy monotonicity of sliding windows, we utilize Cumulative Prefix Sums with Hash Map frequency counting.

CONSTRAINTS & BOUNDS

1≤N≤2×1041 \le N \le 2 \times 10^41≤N≤2×104, nums[i]∈[−1000,1000]nums[i] \in [-1000, 1000]nums[i]∈[−1000,1000]. O(N)O(N)O(N) time with hash map.

FAANG PRODUCTION TRAPS & EDGE CASES

Outside Python the prefix sums need a wide type: with N=2×104N = 2 \times 10^4N=2×104 values of size up to 100010001000, running_sum stays within 2×1072 \times 10^72×107 here, but the same code on larger inputs overflows 32 bits; use 64-bit sums.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•