The problem
Given an array of non-negative integers nums and an integer k, return True if there is a contiguous subarray of length at least 2 whose sum is a multiple of k (0 counts as a multiple). Otherwise return False.
Input: nums = [23, 2, 4, 6, 7], k = 6
Output: TrueThe subarray [2, 4] sums to 6, a multiple of 6, and has length 2.
How to think about it
Start with brute force. Check every subarray of length 2 or more with a running sum: O(n²). With n up to 10⁵ that is too slow.
The key insight: same remainder means divisible difference. The sum of nums[a+1..b] is pre[b] - pre[a], and that is a multiple of k exactly when both prefix sums have the same remainder mod k. So as you walk, compute r = s % k and ask “have I seen this remainder before?”
The length rule decides what to store. A repeat at indices a and b gives a subarray of length b - a. You need b - a >= 2, and the earlier a is, the more likely that holds. So store the first index of each remainder and never overwrite it. Seed with {0: -1} so a prefix that is itself a multiple of k (starting at index 0) counts.
Which pattern is this? Prefix sums plus a hash map. The tell is “subarray sum divisible by / multiple of k”. It is the remainder trick from Subarray Sums Divisible by K, with a first-index map instead of a count map because of the length condition.
Approach
- Set
rem = {0: -1}ands = 0. - For each index
iand valuex, addxtosand computer = s % k. - If
ris inremandi - rem[r] >= 2, returnTrue. - If
ris not inrem, storerem[r] = i. If it is there but too close, keep the older index. - After the loop, return
False.
Solution
def check_subarray_sum(nums, k):
rem = {0: -1}; s = 0 # remainder 0 seen before the array starts
for i, x in enumerate(nums):
s += x; r = s % k
if r in rem:
if i - rem[r] >= 2: return True # same remainder, gap of 2+ elements
else:
rem[r] = i # store only the first index
return False
# check_subarray_sum([23,2,4,6,7], 6) -> TrueDry run
nums = [23, 2, 4, 6, 7], k = 6:
i |
x |
s |
r = s % 6 |
Action |
|---|---|---|---|---|
| 0 | 23 | 23 | 5 | new, store rem[5] = 0 |
| 1 | 2 | 25 | 1 | new, store rem[1] = 1 |
| 2 | 4 | 29 | 5 | seen at 0, gap 2 → return True |
The subarray found is indices 1 to 2, [2, 4].
Complexity
- Time: O(n). One pass with O(1) average map work per element.
- Space: O(min(n, k)). At most
kdistinct remainders, and at most one new key per element.
Edge cases and pitfalls
- A single element that is a multiple of
kdoes not count:[6]withk = 6returnsFalsebecause the length is 1. - Two zeros in a row (
[0, 0]) returnTruefor anyk, since 0 is a multiple of everyk. - Overwriting the stored index is the classic bug:
[5, 0, 0]withk = 3needs the first index of remainder 2 to see the gap of 2. - Using a count map (as in LeetCode 974) loses the index information you need for the length check.
k = 0would makes % kraiseZeroDivisionError. The constraints guaranteek >= 1, but say so out loud in an interview.
Related problems
- Subarray Sums Divisible by K (LeetCode 974): the same remainder idea, counting every divisible subarray.
- Contiguous Array (LeetCode 525): the same first-index map, on a ±1 running sum.
- Maximum Size Subarray Sum Equals k (LeetCode 325): first index of each prefix sum, for the longest match.
- 1.Range Sum Query - Immutable (LeetCode 303)
- 2.Find Pivot Index (LeetCode 724)
- 3.Minimum Value to Get Positive Step by Step Sum (LeetCode 1413)
- 4.Subarray Sum Equals K (LeetCode 560)
- 5.Product of Array Except Self (LeetCode 238)
- 6.Contiguous Array (LeetCode 525)
- 7.Subarray Sums Divisible by K (LeetCode 974)
- 8.Continuous Subarray Sum (LeetCode 523)
- 9.Range Sum Query 2D - Immutable (LeetCode 304)
- 10.Maximum Size Subarray Sum Equals k (LeetCode 325)