title: Continuous Subarray Sum
slug: continuous-subarray-sum
difficulty: medium
leetcode_id: 523
leetcode_url: https://leetcode.com/problems/continuous-subarray-sum/
categories:
  - arrays
  - hash-tables
  - math
patterns:
  - prefix-sum

description: |
  Given an integer array `nums` and an integer `k`, return `true` if `nums` has a **good subarray** or `false` otherwise.

  A **good subarray** is a subarray where:

  - its length is **at least two**, and
  - the sum of the elements of the subarray is a multiple of `k`.

  **Note** that:

  - A **subarray** is a contiguous part of the array.
  - An integer `x` is a multiple of `k` if there exists an integer `n` such that `x = n * k`. `0` is **always** a multiple of `k`.

constraints: |
  - `1 <= nums.length <= 10^5`
  - `0 <= nums[i] <= 10^9`
  - `0 <= sum(nums[i]) <= 2^31 - 1`
  - `1 <= k <= 2^31 - 1`

examples:
  - input: "nums = [23,2,4,6,7], k = 6"
    output: "true"
    explanation: "[2, 4] is a continuous subarray of size 2 whose elements sum up to 6."
  - input: "nums = [23,2,6,4,7], k = 6"
    output: "true"
    explanation: "[23, 2, 6, 4, 7] is a continuous subarray of size 5 whose elements sum up to 42. 42 is a multiple of 6 because 42 = 7 * 6."
  - input: "nums = [23,2,6,4,7], k = 13"
    output: "false"
    explanation: "No contiguous subarray of length at least 2 has a sum that is a multiple of 13."

explanation:
  intuition: |
    This problem seems to ask us to check every possible subarray sum, but that would be too slow. The key insight comes from **modular arithmetic** and **prefix sums**.

    Imagine you're tracking a running total as you walk through the array. At each position, you calculate the prefix sum up to that point. Now, here's the crucial observation: if two prefix sums have the **same remainder when divided by `k`**, then the subarray between them has a sum that's a **multiple of `k`**.

    Think of it like this: if `prefix[i] % k == prefix[j] % k` where `j > i`, then:
    - `prefix[j] - prefix[i]` gives the sum of elements from index `i+1` to `j`
    - Since both have the same remainder, their difference is divisible by `k`

    For example, with `k = 6`: if `prefix[2] = 25` (remainder 1) and `prefix[5] = 49` (remainder 1), then the sum from index 3 to 5 is `49 - 25 = 24`, which is divisible by 6.

    So instead of checking all subarray sums, we just need to find two positions with the **same prefix sum remainder** that are at least 2 indices apart.

  approach: |
    We solve this using a **Prefix Sum with Hash Map** approach:

    **Step 1: Initialise the hash map**

    - Create a hash map `remainder_index` to store the first index where each remainder was seen
    - Set `remainder_index[0] = -1` to handle the case where the subarray starts from index 0
    - Initialise `prefix_sum = 0` to track the running total

    &nbsp;

    **Step 2: Iterate through the array**

    - For each element at index `i`, add it to `prefix_sum`
    - Calculate `remainder = prefix_sum % k`
    - If this remainder exists in our hash map:
      - Check if `i - remainder_index[remainder] >= 2` (subarray length at least 2)
      - If yes, return `true` — we found a valid subarray
    - If this remainder is not in the hash map:
      - Store it with the current index: `remainder_index[remainder] = i`
      - We only store the *first* occurrence to maximise subarray length

    &nbsp;

    **Step 3: Return the result**

    - If we complete the loop without finding a valid subarray, return `false`

    &nbsp;

    The key optimisation is that we only store the **first occurrence** of each remainder. This ensures that when we find a matching remainder later, the subarray between them is as long as possible, giving us the best chance of meeting the length requirement.

  common_pitfalls:
    - title: The Brute Force Trap
      description: |
        A naive approach would check every possible subarray:
        - Outer loop `i` from `0` to `n-1`
        - Inner loop `j` from `i+1` to `n-1`
        - Calculate sum from `i` to `j` and check if divisible by `k`

        This results in **O(n^2) time complexity** (or O(n^3) if summing naively). With `n = 10^5`, this means up to 10 billion operations — guaranteed **Time Limit Exceeded**.
      wrong_approach: "Nested loops checking all subarray sums"
      correct_approach: "Prefix sum with hash map for O(n) time"

    - title: Forgetting the Base Case
      description: |
        The hash map must be initialised with `{0: -1}`, not empty. This handles subarrays that start from index 0.

        For example, with `nums = [6, 1]` and `k = 6`:
        - After index 0: `prefix_sum = 6`, `remainder = 0`
        - Without `{0: -1}`, we wouldn't find a match
        - With `{0: -1}`, we check `0 - (-1) = 1`, which fails the length check
        - After index 1: `prefix_sum = 7`, `remainder = 1`
        - But with `nums = [6, 6]`, after index 1: `remainder = 0`, and `1 - (-1) = 2` passes!
      wrong_approach: "Starting with an empty hash map"
      correct_approach: "Initialise with {0: -1} for subarrays starting at index 0"

    - title: Updating Instead of Keeping First Index
      description: |
        When we see a remainder we've seen before, we should NOT update the hash map. We want the **earliest** index for each remainder to maximise the subarray length.

        If we keep updating, we might miss valid subarrays:
        - `remainder = 3` first seen at index 1
        - `remainder = 3` seen again at index 2 (if we update, we lose index 1)
        - `remainder = 3` seen at index 4: checking against index 2 gives length 2, but index 1 would give length 3
      wrong_approach: "Always updating the hash map with current index"
      correct_approach: "Only store the first occurrence of each remainder"

    - title: Off-by-One in Length Check
      description: |
        The problem requires subarray length **at least 2**, not just "more than 1". The check should be `i - remainder_index[remainder] >= 2`.

        With indices `i` and `j` where `j < i`, the subarray from `j+1` to `i` has length `i - j`. So we need `i - j >= 2`.
      wrong_approach: "Checking `> 1` or `> 2` incorrectly"
      correct_approach: "Check `i - stored_index >= 2`"

  key_takeaways:
    - "**Prefix sum + hash map**: A powerful combination for subarray sum problems. Store prefix sums (or their remainders) to find subarrays with specific properties in O(n) time."
    - "**Modular arithmetic insight**: If `prefix[i] % k == prefix[j] % k`, then the sum between them is divisible by `k`. This transforms a sum problem into a remainder-matching problem."
    - "**Base case matters**: Initialising with `{0: -1}` handles subarrays starting from index 0. Always consider edge cases involving the array start."
    - "**Related problems**: This pattern applies to [Subarray Sum Equals K](/questions/subarray-sum-equals-k), Subarray Sums Divisible by K, and other prefix sum problems."

  time_complexity: "O(n). We traverse the array once, and hash map operations (insert, lookup) are O(1) on average."
  space_complexity: "O(min(n, k)). The hash map stores at most `k` distinct remainders (0 to k-1), or `n` entries if `n < k`."

solutions:
  - approach_name: Prefix Sum with Hash Map
    is_optimal: true
    code: |
      def check_subarray_sum(nums: list[int], k: int) -> bool:
          # Map: remainder -> first index where this remainder was seen
          # {0: -1} handles subarrays starting from index 0
          remainder_index = {0: -1}
          prefix_sum = 0

          for i, num in enumerate(nums):
              # Update running prefix sum
              prefix_sum += num

              # Get remainder when divided by k
              remainder = prefix_sum % k

              # Have we seen this remainder before?
              if remainder in remainder_index:
                  # Check if subarray length is at least 2
                  if i - remainder_index[remainder] >= 2:
                      return True
                  # Don't update - keep the earliest index
              else:
                  # First time seeing this remainder - store the index
                  remainder_index[remainder] = i

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

      **Space Complexity:** O(min(n, k)) — At most `k` distinct remainders can exist.

      We use the mathematical property that two prefix sums with the same remainder mod `k` define a subarray whose sum is divisible by `k`. By storing only the first occurrence of each remainder, we maximise our chances of finding a subarray of length at least 2.

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

          # Try every starting position
          for i in range(n - 1):
              # Accumulate sum for subarrays starting at i
              subarray_sum = nums[i]

              # Try every ending position (at least 2 elements)
              for j in range(i + 1, n):
                  subarray_sum += nums[j]

                  # Check if sum is multiple of k
                  if subarray_sum % k == 0:
                      return True

          return False
    explanation: |
      **Time Complexity:** O(n^2) — Nested loops checking all subarrays.

      **Space Complexity:** O(1) — Only tracking the running sum.

      This approach checks every possible subarray of length at least 2. While correct, it exceeds time limits for large inputs (n = 10^5). Included to illustrate why the prefix sum approach is necessary.
