Continuous Subarray Sum

69.8k
0

Given an integer array nums and an integer k, determine whether the array contains a contiguous subarray that satisfies both conditions:

  • The subarray contains at least two elements.

  • The subarray sum is a multiple of k.

Return true when such a subarray exists. Otherwise, return false.

Example 1

Input: nums = [23, 2, 4, 6, 7], k = 6

Output: true

Explanation: The subarray [2, 4] has sum 6, and 6 is a multiple of 6.

Example 2

Input: nums = [23, 2, 6, 4, 7], k = 13

Output: false

Explanation: There is no subarray of size at least 2 whose sum is a multiple of 13.

Brute Force Approach

The most direct method generates every contiguous subarray containing at least two elements.

For every selected range, the complete sum is calculated separately. The method returns true as soon as a range has a sum divisible by k.

Algorithm

  • Store the array size in n. If n < 2, return false because a valid subarray must contain at least two elements.

  • Treat every index start as a possible beginning and every index end from start + 1 to n - 1 as an ending position. Starting from start + 1 guarantees that the selected range contains at least two elements.

  • For every range [start, end], initialize currentSum with 0 and add all elements from start through end to calculate its complete sum.

  • If k != 0, check whether currentSum % k == 0, which means the selected sum is divisible by k.

  • If k == 0, check whether currentSum == 0 instead, since modulo by zero cannot be performed.

  • Return true immediately when a valid subarray is found. Return false after all possible ranges have been examined.

Dry Run

Continuous Subarray Sum Brute Force Dry Run.png

Continuous Subarray Sum Brute Force Dry Run.png

Solution

#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
// At least two elements are required.
if (n < 2) {
return false;
}
for (int start = 0; start < n - 1; start++) {
for (int end = start + 1; end < n; end++) {
long long currentSum = 0;
// Calculate the sum of the selected range.
for (int index = start; index <= end; index++) {
currentSum += nums[index];
}
/*
* For k = 0, only a zero-sum
* subarray satisfies the condition.
*/
if (k == 0) {
if (currentSum == 0) {
return true;
}
}
else if (currentSum % k == 0) {
return true;
}
}
}
return false;
}
};
int main() {
vector<int> nums = {23, 2, 4, 6, 7};
int k = 6;
Solution solution;
cout << (solution.checkSubarraySum(nums, k) ? "true" : "false");
return 0;
}

Complexity Analysis

Time Complexity: O(N³), where N represents the array size. Two loops select every starting and ending index, while another traversal calculates the sum of each selected range.

Space Complexity: O(1), because only loop indices and currentSum require auxiliary storage.

Better Approach

The Brute Force Approach recalculates every selected range from the beginning.

For one fixed starting index, the next subarray contains the previous range plus one additional element. Maintaining a running sum allows every new range to be checked with one addition.

Algorithm

  • Store the array size in n. If fewer than two elements are available, return false.

  • Treat every index start as the beginning of a new group of subarrays and initialize currentSum with 0.

  • Move end from start to n - 1 and add nums[end] to currentSum. This extends the previous range by one element instead of recalculating its complete sum.

  • Check the divisibility condition only when end - start + 1 >= 2, ensuring that single-element ranges are never accepted.

  • If k != 0, return true when currentSum % k == 0. If k == 0, return true when currentSum == 0.

  • Return false if every possible subarray has been processed without finding a valid one.

Dry Run

Continuous Subarray Sum Better Approach Dry Run.png

Continuous Subarray Sum Better Approach Dry Run.png

Solution

#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
// At least two elements are required.
if (n < 2) {
return false;
}
for (int start = 0; start < n - 1; start++) {
long long currentSum = 0;
for (int end = start; end < n; end++) {
currentSum += nums[end];
// Single-element ranges are not valid.
if (end - start + 1 < 2) {
continue;
}
/*
* For k = 0, only a zero-sum
* subarray satisfies the condition.
*/
if (k == 0) {
if (currentSum == 0) {
return true;
}
}
else if (currentSum % k == 0) {
return true;
}
}
}
return false;
}
};
int main() {
vector<int> nums = {23, 2, 4, 6, 7};
int k = 6;
Solution solution;
cout << (solution.checkSubarraySum(nums, k) ? "true" : "false");
return 0;
}

Complexity Analysis

Time Complexity: O(N²), where N represents the array size. Every starting position extends through all possible ending positions.

Space Complexity: O(1), because only loop indices and currentSum require auxiliary storage.

Optimal Approach

Consider two prefix sums ending at different indices.

When both prefix sums have the same remainder after division by k, subtracting the earlier prefix from the later prefix produces a value divisible by k. Therefore, the elements between those prefix positions form a subarray whose sum is a multiple of k.

Only the first index of each remainder should be stored. The earliest index creates the largest possible distance and provides the best chance of satisfying the required subarray length.

When k = 0, remainders cannot be calculated. In that case, repeated prefix sums indicate that the elements between those positions have a sum of 0.

Algorithm

  • Store the array size in n. If n < 2, return false.

  • Create a hash map firstIndex and initialize it with {0: -1}. The index -1 represents the empty prefix before the array, allowing subarrays beginning at index 0 to satisfy the length condition naturally.

  • Initialize prefixSum with 0 and traverse the array from left to right, treating each current index as a possible ending position.

  • Add nums[index] to prefixSum. If k != 0, use the normalized remainder of prefixSum by |k| as the key. If k == 0, use prefixSum itself because equal prefix sums indicate a zero-sum subarray.

  • If the key has appeared before, check whether index - firstIndex[key] >= 2. A distance of at least 2 means the elements between the two prefix positions form a valid subarray.

  • If the key has not appeared before, store its current index. Keeping only its earliest occurrence gives future indices the largest possible distance. Return false if the traversal ends without finding a valid range.

Dry Run

Continuous Subarray Sum Optimal Approach Dry Run.png

Continuous Subarray Sum Optimal Approach Dry Run.png

Solution

#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
// At least two elements are required.
if (n < 2) {
return false;
}
unordered_map<long long, int> firstIndex;
/*
* The empty prefix starts at index -1,
* allowing ranges beginning at index 0.
*/
firstIndex[0] = -1;
long long prefixSum = 0;
long long divisor = llabs((long long)k);
for (int index = 0; index < n; index++) {
prefixSum += nums[index];
long long key;
if (k == 0) {
// Equal prefix sums produce a zero-sum range.
key = prefixSum;
}
else {
/*
* Normalize the remainder so equivalent
* values use the same hash-map key.
*/
key = ((prefixSum % divisor) + divisor) % divisor;
}
if (firstIndex.find(key) != firstIndex.end()) {
/*
* A distance of at least two gives
* a valid subarray length.
*/
if (index - firstIndex[key] >= 2) {
return true;
}
}
else {
// Keep the earliest position for this key.
firstIndex[key] = index;
}
}
return false;
}
};
int main() {
vector<int> nums = {23, 2, 4, 6, 7};
int k = 6;
Solution solution;
cout << (solution.checkSubarraySum(nums, k) ? "true" : "false");
return 0;
}

Complexity Analysis

Time Complexity: O(N) on average, where N represents the array size. Every element requires one hash-map lookup and, when necessary, one insertion.

Space Complexity: O(N), because the hash map may store up to N + 1 distinct remainder or prefix-sum values.

FAQs

Q1. Why do equal prefix remainders produce a sum divisible by k?

If two prefix sums have the same remainder, their difference has remainder 0. That difference represents the sum of the elements between the two prefix positions.

Q2. Why is remainder 0 initially stored at index -1?

Index -1 represents the empty prefix before the array starts. This allows a valid subarray beginning at index 0 to satisfy the length check naturally.

Q3. Why is only the first index of each remainder stored?

The earliest occurrence produces the greatest distance from future positions. Replacing it with a later index could hide a valid subarray of length at least 2.

Q4. Why must negative remainders be normalized?

Some programming languages produce negative modulo results for negative prefix sums. Normalization ensures that equivalent remainder classes use the same hash-map key.

Q5. How should k = 0 be handled?

Modulo cannot be performed with zero. A repeated prefix sum must be searched instead, because equal prefix sums mean the subarray between them has sum 0.

Q6. Can a sliding-window approach solve this problem?

A standard sliding window is not reliable when negative values are allowed. Adding or removing an element does not change the sum in a predictable direction.

Arrays

Read Similar Blogs

Comments0