Continuous Subarray Sum
MedPrompt
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
xis a multiple ofkif there exists an integernsuch thatx = n * k.0is always a multiple ofk.
Example 1:
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.Example 2:
Input: nums = [23,2,6,4,7], k = 6
Output: true
Explanation: [23, 2, 6, 4, 7] is an continuous subarray of size 5 whose elements sum up to 42.
42 is a multiple of 6 because 42 = 7 * 6 and 7 is an integer.Example 3:
Input: nums = [23,2,6,4,7], k = 13
Output: false
Constraints:
1 <= nums.length <= 1050 <= nums[i] <= 1090 <= sum(nums[i]) <= 231 - 11 <= k <= 231 - 1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves checking every possible continuous subarray that has a length of at least two. We use two nested loops to define the start and end of each subarray. The outer loop selects the starting element, and the inner loop extends the subarray to the right. For each subarray, we calculate its sum and check if it's a multiple of k. To avoid re-calculating the sum from scratch for each subarray, we can maintain a running sum within the inner loop.
Algorithm
- Iterate through the array with a starting index
ifrom0tonums.length - 1. - For each
i, initialize acurrentSumwithnums[i]. - Start an inner loop with index
jfromi + 1tonums.length - 1. This ensures the subarray has at least two elements. - In the inner loop, add
nums[j]tocurrentSum. - Check if
currentSumis a multiple ofk. This is true ifcurrentSum % k == 0. - If the condition is met, we have found a "good subarray" and can immediately return
true. - If the loops complete without finding any such subarray, it means no "good subarray" exists, and we return
false.
Walkthrough
We iterate through the array with a starting index i from 0 to nums.length - 1. For each i, we then start an inner loop with index j from i + 1 to nums.length - 1. This ensures the subarray has at least two elements. Inside the inner loop, we maintain a currentSum for the subarray nums[i...j]. We check if this currentSum is a multiple of k. If k is not zero and currentSum % k == 0, we have found a valid subarray and can immediately return true. If the loops complete without finding any such subarray, it means no "good subarray" exists, and we return false.
public class Solution { public boolean checkSubarraySum(int[] nums, int k) { for (int i = 0; i < nums.length; i++) { int currentSum = 0; for (int j = i; j < nums.length; j++) { currentSum += nums[j]; if (j - i + 1 >= 2) { // Check if length is at least 2 if (currentSum % k == 0) { return true; } } } } return false; }}Complexity
Time
O(n^2), where n is the length of `nums`. The two nested loops lead to a quadratic time complexity. For each pair of `(i, j)`, we do a constant number of operations.
Space
O(1). We only use a few variables to store the indices and the current sum, so the space used is constant.
Trade-offs
Pros
It's simple to understand and implement.
It uses constant extra space.
Cons
This approach is too slow for large input arrays and will likely result in a 'Time Limit Exceeded' error on most coding platforms.
A more efficient approach uses the properties of modular arithmetic combined with prefix sums. The core idea is that if the sum of a subarray nums[i..j] is a multiple of k, then (prefixSum[j] - prefixSum[i-1]) % k == 0. This is equivalent to prefixSum[j] % k == prefixSum[i-1] % k.
This means we are looking for two indices, p and q, such that the prefix sums up to these indices have the same remainder when divided by k. We can iterate through the array, calculate the running prefix sum, and use a HashMap to store the first index at which a particular remainder is seen.
Algorithm
- Initialize a
HashMap<Integer, Integer>namedmapto store(remainder, index). - Add an initial entry
map.put(0, -1)to handle subarrays that start at index 0. - Initialize an integer
runningSum = 0. - Iterate through the
numsarray with indexifrom0ton-1. - Update
runningSumby addingnums[i]. - Calculate
remainder = runningSum % k. - Check if
mapcontains the keyremainder. - If it does, get the previous index
prevIndex = map.get(remainder). Ifi - prevIndex >= 2, a valid subarray is found, so returntrue. - If
mapdoes not contain the keyremainder, add the new pair to the map:map.put(remainder, i). - If the loop completes, return
false.
Walkthrough
We use a HashMap to store the remainders of prefix sums modulo k and the index where that remainder was first encountered. We initialize the HashMap with a key-value pair (0, -1). This is a crucial step to handle cases where the subarray starts from index 0. A prefix sum sum(0, i) being a multiple of k means (sum(0, i) - 0) % k == 0. The 0 sum corresponds to a prefix before the array starts, which we can imagine at index -1.
We iterate through the array from i = 0 to n-1, maintaining a runningSum. In each iteration, we add nums[i] to runningSum and calculate the remainder = runningSum % k. We then check if this remainder already exists as a key in our HashMap. If it does, it means we have found two prefix sums (at the current index i and a previous index map.get(remainder)) that have the same remainder. The sum of the elements between these two indices is a multiple of k. Let the previous index be prevIndex. The subarray is from prevIndex + 1 to i, and its length is i - prevIndex. We must ensure the length is at least 2, so we check if i - prevIndex >= 2. If it is, we return true. If the remainder does not exist in the HashMap, we add it along with the current index i. We only add it if it's not present because we want the earliest index for a given remainder to maximize the length i - prevIndex.
import java.util.HashMap;import java.util.Map; public class Solution { public boolean checkSubarraySum(int[] nums, int k) { // map: remainder -> first index Map<Integer, Integer> map = new HashMap<>(); map.put(0, -1); // To handle subarrays starting from index 0 int runningSum = 0; for (int i = 0; i < nums.length; i++) { runningSum += nums[i]; int remainder = runningSum % k; if (map.containsKey(remainder)) { int prevIndex = map.get(remainder); // Check if the subarray has at least length 2 if (i - prevIndex >= 2) { return true; } } else { // Store the first time this remainder is seen map.put(remainder, i); } } return false; }}Complexity
Time
O(n), where n is the length of `nums`. We iterate through the array only once. The HashMap operations (insertion and lookup) take, on average, O(1) time.
Space
O(min(n, k)). In the worst case, the HashMap might store up to `n` entries if all prefix sum remainders are unique. However, there are only `k` possible remainders when dividing by `k`, so the space complexity is bounded by `k`.
Trade-offs
Pros
Highly efficient with linear time complexity.
Passes for large inputs where brute-force would fail.
Cons
Requires extra space for the HashMap.
The logic involving modular arithmetic and the initial
(0, -1)entry can be less intuitive at first glance.
Solutions
Solution
class Solution {public boolean checkSubarraySum(int[] nums, int k) { Map<Integer, Integer> mp = new HashMap<>(); mp.put(0, -1); int s = 0; for (int i = 0; i < nums.length; ++i) { s += nums[i]; int r = s % k; if (mp.containsKey(r) && i - mp.get(r) >= 2) { return true; } if (!mp.containsKey(r)) { mp.put(r, i); } } return false; }}class Solution {public: bool checkSubarraySum(vector<int> &nums, int k) { unordered_map<int, int> mp; mp[0] = -1; int s = 0; for (int i = 0; i < nums.size(); ++i) { s += nums[i]; int r = s % k; if (mp.count(r) && i - mp[r] >= 2) return true; if (!mp.count(r)) mp[r] = i; } return false; }};class Solution: def checkSubarraySum(self, nums: List[int], k: int) -> bool: s = 0 mp = {0: - 1} for i, v in enumerate(nums): s += v r = s % k if r in mp and i - mp[r] >= 2: return True if r not in mp: mp[r] = i return FalseVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.