#+title: Subarrays with Sum Equal to K * Subarray Sum Equals K - Brute Force [algorithm:array] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sum-equals-k/description/][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 #+begin_src c++ int subarraySum(vector& 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; } #+end_src Time: O(n^2), Space: O(1) * Subarray Sum Equals K - Prefix Sum + Hash Map [algorithm:array] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sum-equals-k/description/][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 #+begin_src c++ int subarraySum(vector& nums, int k) { unordered_map 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; } #+end_src Time: O(n), Space: O(n) * Subarray Sum Equals K - Why prefixCount[0] = 1 [algorithm:interview] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sum-equals-k/description/][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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sums-divisible-by-k/description/][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 #+begin_src c++ int subarraysDivByK(vector& nums, int k) { unordered_map 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/maximum-size-subarray-sum-equals-k/description/][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 #+begin_src c++ int maxSubArrayLen(vector& nums, int k) { unordered_map 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sum-equals-k/description/][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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/subarray-sum-equals-k/description/][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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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 | O(n) | | Longest subarray sum = K | Any integers | Prefix sum | unordered_map | 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 | O(n log n) | | Divisible by K | Any integers | Prefix sum + modulo | unordered_map | 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/description/][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 #+begin_src c++ int shortestSubarray(vector& nums, int k) { int n = nums.size(); vector prefix(n + 1, 0); for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i]; deque 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/count-of-range-sum/description/][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 #+begin_src c++ int countRangeSum(vector& nums, int lower, int upper) { int n = nums.size(); vector 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& 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 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/maximum-sum-circular-subarray/description/][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 #+begin_src c++ int maxSubarraySumCircular(vector& 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); } #+end_src Time: O(n), Space: O(1) Two cases: 1. Maximum subarray does NOT wrap — standard Kadane's (maxSum) 2. 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/number-of-submatrices-that-sum-to-target/description/][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 #+begin_src c++ int numSubmatrixSumTarget(vector>& 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 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 prefixCount; prefixCount[0] = 1; int sum = 0; for (int val : colSum) { sum += val; count += prefixCount[sum - target]; prefixCount[sum]++; } } } return count; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Find the length of the longest subarray with at most K distinct elements and sum = K. ** Back #+begin_src c++ int longestSubarray(vector& nums, int k) { unordered_map 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front See [[https://leetcode.com/problems/max-consecutive-ones-iii/description/][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 #+begin_src c++ int longestOnes(vector& 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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 #+begin_src c++ int maxSumTwoNoOverlap(vector& nums, int L, int M) { int n = nums.size(); // Prefix sums for O(1) subarray sum queries vector 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; } #+end_src 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] :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What's the decision tree for choosing the right subarray sum approach? ** Back 1. **Are there negative numbers?** → YES: Prefix sum approach (hash map, BST, or merge sort) → NO: Sliding window is possible 2. **What are you counting?** → Count all: hash_map → Longest: hash_map → Shortest: hash_map or monotonic deque 3. **Is the target a range [lower, upper]?** → YES: Merge sort (count pairs in range) or Fenwick tree / BST 4. **Is it divisible by K?** → YES: hash_map with modulo 5. **Is the array circular?** → YES: Kadane's twice — max(max_subarray, total - min_subarray) 6. **Is it 2D?** → YES: Fix 2 rows, compress to 1D, then prefix sum 7. **Are elements only 0/1?** → YES: Store indices of 1s, or sliding window counting zeros 8. **Are there constraints on distinct elements?** → YES: Sliding window + frequency map 9. **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.