A clear explanation of detecting a subarray whose sum is a multiple of k using prefix sums and modular arithmetic.
Problem Restatement
We are given:
nums
kWe need to determine whether there exists a continuous subarray of length at least 2 whose sum is a multiple of k.
That means we need some subarray sum satisfying:
for some integer n.
Equivalently:
The official problem asks whether such a subarray exists, with the subarray length required to be at least 2. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums, integer k |
| Output | True or False |
| Subarray length | At least 2 |
| Goal | Subarray sum is a multiple of k |
Function shape:
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
...Examples
Example 1:
nums = [23, 2, 4, 6, 7]
k = 6Consider the subarray:
[2, 4]Its sum is:
2 + 4 = 6Since:
6 % 6 = 0the answer is:
TrueExample 2:
nums = [23, 2, 6, 4, 7]
k = 6The whole array sum is:
23 + 2 + 6 + 4 + 7 = 42Since:
42 % 6 = 0the answer is:
TrueExample 3:
nums = [23, 2, 6, 4, 7]
k = 13No continuous subarray of length at least 2 has sum divisible by 13.
So the answer is:
FalseFirst Thought: Check Every Subarray
A direct solution is:
- Generate every subarray
- Compute its sum
- Check divisibility by
k
There are:
O(n^2)subarrays.
Even with prefix sums, checking all subarrays still costs:
O(n^2)We need something faster.
Key Insight
Use prefix sums.
Let:
Then the sum of subarray:
(i + 1) to jis:
We want this value to be divisible by k.
That means:
Rearranging:
This is the crucial observation.
If two prefix sums have the same remainder modulo k, then the subarray between them has sum divisible by k.
Example of the Remainder Trick
Suppose:
| Index | Prefix sum | Prefix mod 6 |
|---|---|---|
| 0 | 23 | 5 |
| 1 | 25 | 1 |
| 2 | 29 | 5 |
At indices 0 and 2, the remainder is the same:
5Subtracting the prefix sums:
29 - 23 = 6which is divisible by 6.
The corresponding subarray is:
[2, 4]Important Length Condition
The subarray must have length at least 2.
If the same remainder appears at indices:
i and jthen the subarray length is:
j - iSo we require:
j - i >= 2Algorithm
Maintain:
| Structure | Meaning |
|---|---|
prefix_mod | Current prefix sum modulo k |
seen | Maps remainder to earliest index |
Initialize:
seen = {0: -1}This handles subarrays starting at index 0.
Then scan the array:
- Update the running prefix remainder
- If this remainder appeared before:
- check whether the distance is at least
2
- check whether the distance is at least
- Otherwise, store the current index as the first occurrence
If a valid subarray is found, return True.
Otherwise return False.
Correctness
Let the running prefix sum up to index i be:
Suppose two indices i and j satisfy:
Then:
is divisible by k.
But:
is exactly the sum of the subarray between those positions.
Therefore the corresponding subarray sum is a multiple of k.
The algorithm stores the earliest occurrence of each remainder. When the same remainder appears again at a later index, the algorithm checks whether the subarray length is at least 2.
If such a pair exists, the algorithm returns True.
If the algorithm finishes without finding such a pair, then no subarray of length at least 2 has sum divisible by k.
Therefore the algorithm is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | One pass through the array |
| Space | O(min(n, k)) | Stores prefix remainders |
Implementation
from typing import List
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
seen = {0: -1}
prefix_mod = 0
for i, num in enumerate(nums):
prefix_mod = (prefix_mod + num) % k
if prefix_mod in seen:
if i - seen[prefix_mod] >= 2:
return True
else:
seen[prefix_mod] = i
return FalseCode Explanation
Store remainder 0 at index -1:
seen = {0: -1}This allows subarrays starting from index 0.
Track the running prefix remainder:
prefix_mod = 0Update it for each element:
prefix_mod = (prefix_mod + num) % kIf this remainder appeared before:
if prefix_mod in seen:then the subarray between the two positions has sum divisible by k.
Check the required length:
if i - seen[prefix_mod] >= 2:
return TrueIf this remainder has never appeared, store its first occurrence:
else:
seen[prefix_mod] = iWe store only the earliest occurrence because it gives the longest possible subarray.
Testing
def test_check_subarray_sum():
s = Solution()
assert s.checkSubarraySum([23, 2, 4, 6, 7], 6) is True
assert s.checkSubarraySum([23, 2, 6, 4, 7], 6) is True
assert s.checkSubarraySum([23, 2, 6, 4, 7], 13) is False
assert s.checkSubarraySum([0, 0], 1) is True
assert s.checkSubarraySum([1, 2, 3], 5) is True
assert s.checkSubarraySum([1, 0], 2) is False
print("all tests passed")Test meaning:
| Test | Why |
|---|---|
| Standard example | Basic divisible subarray |
| Whole array divisible | Large valid subarray |
| No valid subarray | Negative case |
[0, 0] | Zero-sum divisible case |
[2, 3] sum to 5 | Exact multiple |
| Length-1 divisible value | Must reject because length < 2 |