title: Subarray Sum Equals K
slug: subarray-sum-equals-k
difficulty: medium
leetcode_id: 560
leetcode_url: https://leetcode.com/problems/subarray-sum-equals-k/
categories:
  - arrays
  - hash-tables
patterns:
  - prefix-sum

function_signature: "def subarray_sum(nums: list[int], k: int) -> int:"

test_cases:
  visible:
    - input: { nums: [1, 1, 1], k: 2 }
      expected: 2
    - input: { nums: [1, 2, 3], k: 3 }
      expected: 2
  hidden:
    - input: { nums: [1], k: 1 }
      expected: 1
    - input: { nums: [1], k: 0 }
      expected: 0
    - input: { nums: [-1, -1, 1], k: 0 }
      expected: 1
    - input: { nums: [1, -1, 0], k: 0 }
      expected: 3

description: |
  Given an array of integers `nums` and an integer `k`, return *the total number of subarrays whose sum equals to* `k`.

  A subarray is a contiguous **non-empty** sequence of elements within an array.

constraints: |
  - `1 <= nums.length <= 2 * 10^4`
  - `-1000 <= nums[i] <= 1000`
  - `-10^7 <= k <= 10^7`

examples:
  - input: "nums = [1,1,1], k = 2"
    output: "2"
    explanation: "The subarrays [1,1] (indices 0-1) and [1,1] (indices 1-2) both sum to 2."
  - input: "nums = [1,2,3], k = 3"
    output: "2"
    explanation: "The subarrays [1,2] and [3] both sum to 3."

explanation:
  intuition: |
    Imagine you're walking along a number line, keeping a running total of all the numbers you've seen so far. This running total is called a **prefix sum**.

    Here's the key insight: if you've reached a certain prefix sum `current_sum`, and you want to find a subarray ending at this position that sums to `k`, you need to find a *previous* prefix sum that equals `current_sum - k`. Why? Because the difference between two prefix sums gives you the sum of the subarray between them.

    Think of it like this: if your cumulative total at position `j` is 10, and your cumulative total at position `i` was 3, then the sum of elements from index `i+1` to `j` is `10 - 3 = 7`. So if you want a subarray sum of `k = 7`, you look for any previous prefix sum of `current_sum - k = 10 - 7 = 3`.

    The trick is to use a **hash map** to count how many times each prefix sum has occurred. As you iterate through the array, you can instantly look up how many times `current_sum - k` has appeared before — each occurrence represents a valid subarray ending at the current index.

  approach: |
    We solve this using the **Prefix Sum + Hash Map** technique:

    **Step 1: Initialise tracking variables**

    - `count`: Set to `0` to track the total number of valid subarrays
    - `current_sum`: Set to `0` to track the running prefix sum
    - `prefix_counts`: A hash map initialised with `{0: 1}` — this accounts for subarrays that start from index 0

    &nbsp;

    **Step 2: Iterate through the array**

    - Add the current element to `current_sum`
    - Calculate `complement = current_sum - k`
    - If `complement` exists in `prefix_counts`, add its count to our result — each occurrence represents a valid subarray ending here
    - Add `current_sum` to `prefix_counts` (increment its count if it already exists)

    &nbsp;

    **Step 3: Return the result**

    - Return `count` after processing all elements

    &nbsp;

    The hash map initialisation `{0: 1}` is crucial: it handles the case where a prefix sum *exactly* equals `k`, meaning the subarray starts from index 0.

  common_pitfalls:
    - title: The Brute Force Trap
      description: |
        A natural first approach is to check every possible subarray with nested loops:
        - Outer loop for start index
        - Inner loop for end index
        - Calculate sum for each subarray

        This results in **O(n^2) time complexity** (or O(n^3) if you recalculate sums each time). With `n = 2 * 10^4`, this means up to 400 million operations — too slow!
      wrong_approach: "Nested loops checking every subarray"
      correct_approach: "Prefix sum with hash map for O(n) lookup"

    - title: Forgetting to Initialise the Hash Map with Zero
      description: |
        If you start with an empty hash map, you'll miss subarrays that begin at index 0.

        For example, with `nums = [3]` and `k = 3`: the prefix sum after the first element is 3, and we need `current_sum - k = 3 - 3 = 0` to exist in our map. Without `{0: 1}` initialisation, we'd return 0 instead of 1.
      wrong_approach: "Starting with an empty hash map"
      correct_approach: "Initialise with {0: 1} to handle subarrays starting at index 0"

    - title: Using Sliding Window Instead
      description: |
        Sliding window doesn't work here because the array can contain **negative numbers**. With negative numbers, expanding the window doesn't guarantee the sum increases, and shrinking doesn't guarantee it decreases.

        Sliding window is only valid when all elements are positive (or all negative), where the sum is monotonic with window size.
      wrong_approach: "Sliding window technique"
      correct_approach: "Prefix sum with hash map (handles negative numbers)"

    - title: Updating the Hash Map Before Checking
      description: |
        The order of operations matters. You must check for `current_sum - k` in the hash map *before* adding `current_sum` to the map.

        If you add first, you might count a subarray of length 0 (from an index to itself) when `k = 0`.
      wrong_approach: "Adding current_sum to map before checking complement"
      correct_approach: "Check complement first, then add current_sum to map"

  key_takeaways:
    - "**Prefix sum pattern**: The difference between two prefix sums gives the sum of elements in between — a powerful technique for range sum queries"
    - "**Hash map for O(1) lookups**: Instead of searching for complements with nested loops, store counts in a hash map"
    - "**Initialisation matters**: Starting with `{0: 1}` handles edge cases where the subarray starts at index 0"
    - "**When sliding window fails**: This problem demonstrates why sliding window requires monotonic relationships — negative numbers break that assumption"

  time_complexity: "O(n). We traverse the array once, with O(1) hash map operations at each step."
  space_complexity: "O(n). In the worst case, all prefix sums are unique, requiring O(n) space in the hash map."

solutions:
  - approach_name: Prefix Sum with Hash Map
    is_optimal: true
    code: |
      def subarray_sum(nums: list[int], k: int) -> int:
          # Count of valid subarrays found
          count = 0
          # Running prefix sum
          current_sum = 0
          # Map of prefix_sum -> count of occurrences
          # Initialise with 0:1 to handle subarrays starting at index 0
          prefix_counts = {0: 1}

          for num in nums:
              # Update running sum
              current_sum += num

              # Check if (current_sum - k) exists in our map
              # If so, there are that many subarrays ending here with sum k
              complement = current_sum - k
              if complement in prefix_counts:
                  count += prefix_counts[complement]

              # Add current prefix sum to the map
              prefix_counts[current_sum] = prefix_counts.get(current_sum, 0) + 1

          return count
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array with O(1) hash map operations.

      **Space Complexity:** O(n) — Hash map stores up to n unique prefix sums.

      We use the mathematical insight that `sum(i, j) = prefix_sum(j) - prefix_sum(i-1)`. By storing prefix sum counts in a hash map, we can instantly find how many previous positions would create a valid subarray ending at the current position.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      def subarray_sum(nums: list[int], k: int) -> int:
          count = 0
          n = len(nums)

          # Try every possible starting index
          for i in range(n):
              current_sum = 0
              # Try every possible ending index
              for j in range(i, n):
                  current_sum += nums[j]
                  # Check if this subarray sums to k
                  if current_sum == k:
                      count += 1

          return count
    explanation: |
      **Time Complexity:** O(n^2) — Nested loops check all possible subarrays.

      **Space Complexity:** O(1) — Only tracking count and current_sum.

      This approach checks every contiguous subarray by trying all start/end combinations. While correct, it's too slow for large inputs. Included to show why the prefix sum optimisation is necessary.
