17 KiB
Subarrays with Sum Equal to K
- Subarray Sum Equals K - Brute Force [algorithm:array]
- Subarray Sum Equals K - Prefix Sum + Hash Map [algorithm:array]
- Subarray Sum Equals K - Why prefixCount[0] = 1 [algorithm:interview]
- Subarray Sum Divisible by K [algorithm:array]
- Longest Subarray Sum Equals K [algorithm:array]
- Subarray Sum Equals K - Negative Numbers? [algorithm:interview]
- Subarray Sum Equals K - Sliding Window Limitation [algorithm:interview]
- Subarray Sum Variations — Master Table [algorithm:array]
- Subarray Shortest Sum ≥ K — Monotonic Deque [algorithm:array]
- Subarray Sum in Range [lower, upper] [algorithm:array]
- Max Circular Subarray Sum [algorithm:array]
- 2D Matrix Subarray Sum = K [algorithm:array]
- Exactly K Distinct Elements with Sum = K [algorithm:array]
- Binary Array — Exactly K Ones [algorithm:array]
- Two Non-Overlapping Subarrays Each Sum = K [algorithm:array]
- Subarray Sum — All Relationships Diagram [algorithm:interview]
Subarray Sum Equals K - Brute Force [algorithm:array]
Front
See LC 560. Find the total number of continuous subarrays whose sum equals k. Solve using the brute force approach. Example: nums = [1,2,3], k = 3 → Output: 2 ([1,2] and [3])
Back
int subarraySum(vector<int>& nums, int k) {
int count = 0;
for (int i = 0; i < nums.size(); i++) {
int sum = 0;
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
if (sum == k) count++;
}
}
return count;
}
Time: O(n^2), Space: O(1)
Subarray Sum Equals K - Prefix Sum + Hash Map [algorithm:array]
Front
See LC 560. Find the total number of continuous subarrays whose sum equals k. Solve optimally using prefix sum with a hash map. Example: nums = [1,2,3], k = 3 → Output: 2
Back
int subarraySum(vector<int>& nums, int k) {
unordered_map<int,int> prefixCount;
prefixCount[0] = 1; // base case: sum of 0 appears once
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
// If (sum - k) exists, those prefixes form valid subarrays
count += prefixCount[sum - k];
prefixCount[sum]++;
}
return count;
}
Time: O(n), Space: O(n)
Subarray Sum Equals K - Why prefixCount[0] = 1 [algorithm:interview]
Front
See LC 560. In the optimal subarray sum solution (prefix sum + hash map), why is `prefixCount[0] = 1` initialized?
Back
It handles the case where a subarray starts from index 0 and its sum equals k.
When sum == k at some index, we look up prefixCount[sum - k] = prefixCount[0]. Without the initialization, this lookup would return 0, missing valid subarrays like [3] where k = 3.
The base case means: "a prefix sum of 0 exists once (before any element)" — conceptually the empty prefix.
Subarray Sum Divisible by K [algorithm:array]
Front
See LC 974. Find the total number of continuous subarrays whose sum is divisible by k. Example: nums = [23,2,4,6,7], k = 6 → Output: 2 ([2,4] and [7])
Back
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int,int> prefixCount;
prefixCount[0] = 1;
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
// Modulo can be negative in C++, fix with ((sum % k) + k) % k
int remainder = ((sum % k) + k) % k;
count += prefixCount[remainder];
prefixCount[remainder]++;
}
return count;
}
Key insight: If two prefix sums have the same remainder mod k, their subarray sum is divisible by k.
Time: O(n), Space: O(min(n, k))
Longest Subarray Sum Equals K [algorithm:array]
Front
See LC 325. Find the maximum length of a contiguous subarray that sums to k. Example: nums = [1,-1,5,-2,3], k = 3 → Output: 4 ([1,-1,5,-2])
Back
int maxSubArrayLen(vector<int>& nums, int k) {
unordered_map<int,int> firstOccurrence;
firstOccurrence[0] = 0; // sum 0 at index 0 (1-based)
int sum = 0, maxLen = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
// Check if subarray ending here sums to k
if (firstOccurrence.count(sum - k)) {
maxLen = max(maxLen, i + 1 - firstOccurrence[sum - k]);
}
// Store first occurrence only (for longest)
if (!firstOccurrence.count(sum)) {
firstOccurrence[sum] = i + 1;
}
}
return maxLen;
}
Key difference from counting: store only the first occurrence of each prefix sum to maximize length.
Time: O(n), Space: O(n)
Subarray Sum Equals K - Negative Numbers? [algorithm:interview]
Front
See LC 560. Does the prefix sum + hash map approach for "subarray sum equals k" work when the array contains negative numbers? Why?
Back
Yes, it works with negative numbers.
The prefix sum approach does NOT require positive-only elements. It works for any integer array because:
- prefix[j] - prefix[i] = sum[i+1..j] is always true regardless of sign
- The hash map tracks ALL prefix sums seen, positive or negative
- We only need (sum - k) to exist in the map
This makes it superior to the sliding window approach, which only works for non-negative arrays.
Subarray Sum Equals K - Sliding Window Limitation [algorithm:interview]
Front
See LC 560. Can you use the sliding window technique to solve "subarray sum equals k"? When does it work?
Back
Sliding window ONLY works when all elements are non-negative (positive or zero).
When all elements >= 0:
- Expanding the window increases the sum
- Shrinking the window decreases the sum
- This monotonicity lets us adjust the window
Sliding window fails with negative numbers because adding/removing elements doesn't monotonically change the sum.
Prefer prefix sum + hash map — it works for all cases (positive, negative, zero).
Subarray Sum Variations — Master Table [algorithm:array]
Front
What's the master table of subarray sum variations and their approaches?
Back
| Question | Constraint | Approach | Key Data Structure | Time |
|---|---|---|---|---|
| Count subarrays sum = K | Any integers | Prefix sum | unordered_map<sum, frequency> | O(n) |
| Longest subarray sum = K | Any integers | Prefix sum | unordered_map<sum, first_index> | O(n) |
| Shortest subarray sum ≥ K | Any integers | Prefix sum + monotonic deque | deque of indices | O(n) |
| Shortest subarray sum = K | Any integers | Prefix sum + ordered map | map<sum, last_index> | O(n log n) |
| Divisible by K | Any integers | Prefix sum + modulo | unordered_map<remainder, freq> | O(n) |
| Sum in range [lower, upper] | Any integers | Prefix sum + BST | multiset / Fenwick / segment tree | O(n log n) |
| Max circular subarray sum | Any integers | Kadane's + total - min_subarray | 2x Kadane | O(n) |
| Subarray sum with only positives | Positives only | Sliding window | Two pointers | O(n) |
| 2D matrix subarray sum = K | 2D grid | Fix rows, compress to 1D | Prefix sum on compressed row | O(n^3) |
| At most K distinct elements | Frequency constraint | Sliding window + freq map | Hash map of counts | O(n) |
| Subarray with exactly K ones | Binary array | Store indices of 1s | Vector of positions | O(n) |
| Two non-overlapping subarrays each = K | Any integers | Prefix sum + track both sides | 2 pass with prefix map | O(n) |
Core pattern: prefix[j] - prefix[i] = subarray(i+1..j). The variation changes what you store and query.
Subarray Shortest Sum ≥ K — Monotonic Deque [algorithm:array]
Front
See LC 862. Find the length of the shortest contiguous subarray with sum ≥ K. Works with negative numbers. Example: nums = [84,-37,32,40,95], K = 167 → Output: 3
Back
int shortestSubarray(vector<int>& nums, int k) {
int n = nums.size();
vector<long long> prefix(n + 1, 0);
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
deque<int> dq; // stores indices, prefix[dq[j]] is increasing
int minLen = INT_MAX;
for (int i = 0; i <= n; i++) {
// If prefix[i] - prefix[dq.front()] >= K, pop front and record length
while (!dq.empty() && prefix[i] - prefix[dq.front()] >= k) {
minLen = min(minLen, i - dq.front());
dq.pop_front();
}
// Maintain monotonic increasing order of prefix values in deque
while (!dq.empty() && prefix[i] < prefix[dq.back()]) {
dq.pop_back();
}
dq.push_back(i);
}
return minLen == INT_MAX ? -1 : minLen;
}
Time: O(n), Space: O(n)
Key insight: Maintain a deque of indices where prefix sums are increasing. If prefix[i] - prefix[dq.front()] ≥ K, then any index after dq.front() gives a shorter valid subarray.
Subarray Sum in Range [lower, upper] [algorithm:array]
Front
See LC 327. Count the number of subarrays whose sum lies in range [lower, upper]. Example: nums = [-2,5,-1], lower = -2, upper = 2 → Output: 3 ([-2], [-1], [5,-1])
Back
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n = nums.size();
vector<long long> prefix(n + 1, 0);
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
// Use merge sort to count valid pairs
return mergeCount(prefix, 0, n, lower, upper);
}
int mergeCount(vector<long long>& P, int lo, int hi, int lower, int upper) {
if (lo >= hi - 1) return 0;
int mid = lo + (hi - lo) / 2;
int count = mergeCount(P, lo, mid, lower, upper)
+ mergeCount(P, mid, hi, lower, upper);
// Count valid (i, j) pairs where P[j] - P[i] in [lower, upper]
int j = mid, k = mid, t = mid;
for (int i = lo; i < mid; i++) {
while (k <= hi && P[k] - P[i] < lower) k++;
while (t <= hi && P[t] - P[i] <= upper) t++;
count += (t - k);
}
// Merge step
vector<long long> temp;
int p1 = lo, p2 = mid;
while (p1 < mid && p2 < hi) {
if (P[p1] <= P[p2]) temp.push_back(P[p1++]);
else temp.push_back(P[p2++]);
}
while (p1 < mid) temp.push_back(P[p1++]);
while (p2 < hi) temp.push_back(P[p2++]);
for (int i = 0; i < (int)temp.size(); i++) P[lo + i] = temp[i];
return count;
}
Time: O(n log n), Space: O(n)
Why not hash map? Hash map can't count how many prefix sums fall in a range. Merge sort counts (i, j) pairs where P[j] - P[i] ∈ [lower, upper] during the merge step.
Max Circular Subarray Sum [algorithm:array]
Front
See LC 918. Find the maximum sum of a non-empty subarray in a circular array. Example: nums = [1,-2,3,-2] → Output: 3 Example: nums = [5,-3,5] → Output: 10 (wraps around: [5, 5])
Back
int maxSubarraySumCircular(vector<int>& nums) {
int total = 0, maxSum = nums[0], curMax = 0;
int minSum = nums[0], curMin = 0;
for (int num : nums) {
curMax = max(curMax + num, num);
maxSum = max(maxSum, curMax);
curMin = min(curMin + num, num);
minSum = min(minSum, curMin);
total += num;
}
// If all numbers are negative, maxSum is the answer (total - minSum = 0)
return maxSum < 0 ? maxSum : max(maxSum, total - minSum);
}
Time: O(n), Space: O(1)
Two cases:
- Maximum subarray does NOT wrap — standard Kadane's (maxSum)
- Maximum subarray DOES wrap — total - minSubarray (remove the minimum subarray from the middle)
Edge case: all negative → total - minSum = 0, which is wrong. Return maxSum instead.
2D Matrix Subarray Sum = K [algorithm:array]
Front
See Submatrices Sum to Target. Given a 2D matrix, find the number of submatrices whose sum equals K. Example: matrix = 0,1,0],[1,1,1],[0,1,0, K = 0 → Output: 4
Back
int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int count = 0;
// Fix top and bottom rows, compress to 1D array
for (int r1 = 0; r1 < m; r1++) {
vector<int> colSum(n, 0);
for (int r2 = r1; r2 < m; r2++) {
// Add row r2 to the compressed column sums
for (int c = 0; c < n; c++) {
colSum[c] += matrix[r2][c];
}
// Now solve 1D subarray sum = target
unordered_map<int,int> prefixCount;
prefixCount[0] = 1;
int sum = 0;
for (int val : colSum) {
sum += val;
count += prefixCount[sum - target];
prefixCount[sum]++;
}
}
}
return count;
}
Time: O(m^2 * n), Space: O(n)
Key idea: Fix two rows (r1, r2), compress columns between them into a 1D array. Then the problem reduces to 1D subarray sum = K.
Exactly K Distinct Elements with Sum = K [algorithm:array]
Front
Find the length of the longest subarray with at most K distinct elements and sum = K.
Back
int longestSubarray(vector<int>& nums, int k) {
unordered_map<int,int> freq;
int sum = 0, distinct = 0, maxLen = 0;
int left = 0;
for (int right = 0; right < nums.size(); right++) {
if (freq[nums[right]] == 0) distinct++;
freq[nums[right]]++;
sum += nums[right];
// Shrink while distinct > K or sum > k
while (distinct > k || sum > k) {
freq[nums[left]]--;
if (freq[nums[left]] == 0) distinct--;
sum -= nums[left];
left++;
}
if (sum == k) maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}
Time: O(n), Space: O(min(n, K))
This combines sliding window (distinct elements constraint) with sum check. The two constraints (distinct count + sum) make it trickier than simple sliding window.
Binary Array — Exactly K Ones [algorithm:array]
Front
See LC 1004. Given a binary array nums and an integer k, return the maximum number of consecutive 1's in the array if you can flip at most k 0's. Example: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2 → Output: 6
Back
int longestOnes(vector<int>& nums, int k) {
int left = 0, maxLen = 0;
for (int right = 0; right < nums.size(); right++) {
if (nums[right] == 0) k--;
while (k < 0) {
if (nums[left] == 0) k++;
left++;
}
maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}
Time: O(n), Space: O(1)
Sliding window: expand right, count zeros. When zeros exceed k, shrink from left.
Two Non-Overlapping Subarrays Each Sum = K [algorithm:array]
Front
Find the maximum sum of two non-overlapping subarrays with lengths L and M. Example: nums = [0,6,5,2,2,5,1,9,4], L = 1, M = 2 → Output: 20 ([9] and [6,5])
Back
int maxSumTwoNoOverlap(vector<int>& nums, int L, int M) {
int n = nums.size();
// Prefix sums for O(1) subarray sum queries
vector<int> prefix(n + 1, 0);
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
auto sum = [&](int i, int j) { return prefix[j + 1] - prefix[i]; };
int maxL = 0, maxM = 0, result = 0;
// Case 1: L comes before M
for (int i = L + M; i <= n; i++) {
maxL = max(maxL, sum(i - L - M, i - M - 1));
maxM = max(maxM, sum(i - M, i - 1));
result = max(result, maxL + maxM);
}
// Case 2: M comes before L
maxL = 0; maxM = 0; result = 0;
for (int i = L + M; i <= n; i++) {
maxM = max(maxM, sum(i - L - M, i - L - 1));
maxL = max(maxL, sum(i - L, i - 1));
result = max(result, maxL + maxM);
}
return result;
}
Time: O(n), Space: O(n)
Two cases: L before M, or M before L. Track the running maximum of the first subarray while computing the second.
Subarray Sum — All Relationships Diagram [algorithm:interview]
Front
What's the decision tree for choosing the right subarray sum approach?
Back
- Are there negative numbers? → YES: Prefix sum approach (hash map, BST, or merge sort) → NO: Sliding window is possible
- What are you counting? → Count all: hash_map<sum, frequency> → Longest: hash_map<sum, first_index> → Shortest: hash_map<sum, last_index> or monotonic deque
- Is the target a range [lower, upper]? → YES: Merge sort (count pairs in range) or Fenwick tree / BST
- Is it divisible by K? → YES: hash_map<remainder, frequency> with modulo
- Is the array circular? → YES: Kadane's twice — max(max_subarray, total - min_subarray)
- Is it 2D? → YES: Fix 2 rows, compress to 1D, then prefix sum
- Are elements only 0/1? → YES: Store indices of 1s, or sliding window counting zeros
- Are there constraints on distinct elements? → YES: Sliding window + frequency map
- Are there multiple non-overlapping subarrays? → YES: 2-pass — track running max of first while computing second
The fundamental identity: subarray(i,j) = prefix[j+1] - prefix[i]. Everything else is about what you do with the prefix array.