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 function_signature: "def check_subarray_sum(nums: list[int], k: int) -> bool:" test_cases: visible: - input: { nums: [23, 2, 4, 6, 7], k: 6 } expected: true - input: { nums: [23, 2, 6, 4, 7], k: 6 } expected: true - input: { nums: [23, 2, 6, 4, 7], k: 13 } expected: false hidden: - input: { nums: [0, 0], k: 1 } expected: true - input: { nums: [1, 0], k: 2 } expected: false - input: { nums: [5, 0, 0, 0], k: 3 } expected: true - input: { nums: [1, 2, 3], k: 5 } expected: true - input: { nums: [1, 2, 12], k: 6 } expected: false - input: { nums: [23, 2, 4, 6, 6], k: 7 } expected: true - input: { nums: [1, 1], k: 1 } expected: true 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   **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   **Step 3: Return the result** - If we complete the loop without finding a valid subarray, return `false`   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.