diff --git a/org/questions/qn_00.org b/org/questions/qn_00.org index eac5cfc..6904eba 100644 --- a/org/questions/qn_00.org +++ b/org/questions/qn_00.org @@ -497,3 +497,267 @@ What's the decision tree for choosing the right subarray sum approach? The fundamental identity: subarray(i,j) = prefix[j+1] - prefix[i]. Everything else is about what you do with the prefix array. + +* Subarray Sum — Core Trigger Heuristic [algorithm:interview] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What is the most reliable heuristic for recognizing when to use Prefix Sums? + +** Back +When you see **"subarray"** and **"sum"** in the same problem description, **Prefix Sums** should be your immediate first thought. + +The core formula: + Sum(i..j) = PrefixSum[j] - PrefixSum[i-1] + +This turns a **range query** into a **difference between two points**, eliminating the need to re-scan the array. + +The mental trigger is simple: subarray + sum → prefix sums. Always. + +* Subarray Sum — When to Use Sliding Window vs Prefix Sum [algorithm:interview] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +When should you use Sliding Window vs Prefix Sum + Hash Map for subarray sum problems? + +** Back +| Condition | Use | Why | +|-----------|-----|-----| +| All numbers ≥ 0, need target sum or max length | **Sliding Window** | Monotonic: expanding always increases sum, shrinking always decreases. O(1) space vs O(n). | +| Numbers can be negative, need exact target sum | **Prefix Sum + Hash Map** | Monotonicity broken. Adding an element could make sum smaller. Must remember past states. | +| Frequent updates between queries | **Fenwick Tree / Segment Tree** | Prefix sum array takes O(n) to update. Trees give O(log n) update + query. | + +Rule of thumb: check for negative numbers first. If none exist, sliding window wins. + +* Subarray Product — Prefix Product Pattern [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +How does the prefix sum pattern translate to subarray **product** problems? + +** Back +The prefix sum pattern translates directly into a **Prefix Product**: + + Product(i..j) = PrefixProduct[j] / PrefixProduct[i-1] + +Instead of subtracting, you divide. + +Edge case: zeros break division. Handle by: +1. Segmentation — split the array at zeros, solve each segment independently +2. Track position of last seen zero to reset boundaries + +Example: count subarrays with product < K (all positive): +#+begin_src c++ +int numSubarrayProductLessThanK(vector& nums, int k) { + if (k == 0) return 0; + int count = 0, product = 1, left = 0; + for (int right = 0; right < nums.size(); right++) { + product *= nums[right]; + while (left <= right && product >= k) product /= nums[left++]; + count += right - left + 1; + } + return count; +} +#+end_src + +* Subarray with Equal 0s and 1s — Value Mapping [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Find the maximum length of a contiguous subarray with equal number of 0s and 1s. +Example: nums = [1,0,1] → Output: 2 ([1,0] or [0,1]) + +** Back +Treat 0 as -1 and 1 as +1. The problem becomes: **"Find the longest subarray whose sum equals 0."** + +#+begin_src c++ +int findMaxLength(vector& nums) { + 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] == 1) ? 1 : -1; + if (firstOccurrence.count(sum)) { + maxLen = max(maxLen, i + 1 - firstOccurrence[sum]); + } else { + firstOccurrence[sum] = i + 1; + } + } + return maxLen; +} +#+end_src +Time: O(n), Space: O(n) + +This is the **"prefix state"** generalization: map values to +1/-1, then it's just prefix sum = 0. + +* Subarray with Equal Odd and Even Numbers [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Find the longest subarray with equal number of odd and even numbers. + +** Back +Map every even number to +1 and every odd number to -1 (or vice versa). The problem becomes: +**"Find the longest subarray whose sum equals 0."** + +Same approach as equal 0s and 1s: prefix sum + hash map storing first occurrence. + +This is the same "prefix state" generalization applied to a different domain. + +* Multi-Category Balance — (A, B, C) Equal Counts [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Find the longest subarray with equal number of 'A's, 'B's, and 'C's. + +** Back +You can't use a single scalar (+1/-1) for three categories. Instead, track **relative differences** between counts. + +Maintain running counts: c_A, c_B, c_C. +At each step, compute the tuple of differences: (c_A - c_B, c_B - c_C). + +If this tuple repeats later in the array, the elements between those indices have perfectly balanced A, B, C counts. + +Data structure: hash map where the key is the state tuple: + map, int> firstOccurrence; + +The tuple (diff_AB, diff_BC) captures the full relative state. If two positions share the same tuple, the subarray between them has zero net change in all three relative differences → equal counts. + +Time: O(n), Space: O(n) + +* Subarray Product Is Positive / Negative [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Find the maximum length of a subarray with a positive (or negative) product. + +** Back +Map: positive → +1, negative → -1, zero → resets the window. + +A subarray has positive product if it contains an **even** number of negatives. +A subarray has negative product if it contains an **odd** number of negatives. + +Track the parity (odd/even count) of negative numbers as you traverse: +- If parity is even at index i and was even at index j (j < i), the subarray (j+1..i) has positive product. +- If parity is odd at index i and was even at index j, the subarray (j+1..i) has negative product. + +Data structure: two hash maps (or arrays) — first occurrence of even-parity index and first occurrence of odd-parity index. + +Time: O(n), Space: O(n) + +* Bitwise Subarray — OR / AND / XOR [algorithm:array] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Can you use prefix sums for subarray problems with Bitwise OR, AND, or XOR? + +** Back +**XOR:** YES — XOR is invertible (XOR is its own inverse). + XOR(i..j) = PrefixXOR[j] ^ PrefixXOR[i-1] +Same hash map pattern as prefix sum. + +**OR / AND:** NO — not invertible. You cannot "undo" an OR or AND operation. + +For OR/AND, exploit the key property: as you expand a subarray, the OR/AND result can only change at most **32 times** (for 32-bit integers) because bits only transition 0→1 (OR) or 1→0 (AND). + +Strategy: maintain a **set** of all possible OR results ending at the current index. The set never exceeds size 32. + +#+begin_src c++ +int subarrayBitwiseORs(vector& arr) { + unordered_set result, current; + for (int x : arr) { + unordered_set next; + next.insert(x); + for (int val : current) { + next.insert(val | x); + } + current = next; + for (int val : current) result.insert(val); + } + return result.size(); +} +#+end_src +Time: O(n * 32), Space: O(32) per step + +* Subarray Sum — Keyword-to-Algorithm Mapping [algorithm:interview] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What is the master keyword-to-algorithm mapping for subarray problems? + +** Back +| Problem Phrase | Array Property | Algorithm | +|---------------|---------------|-----------| +| "Continuous subarray + Sum = K" | Only positive numbers | **Sliding Window** (O(1) space) | +| "Continuous subarray + Sum = K" | Positive & negative | **Prefix Sum + Hash Map** (O(n) space) | +| "Divisible by K" or "Multiple of X" | Any numbers | **Prefix Remainder + Hash Map** (sum % K) | +| "Equal number of X and Y" | Any numbers | **Value Mapping** (X→1, Y→-1) + Prefix Sum Map | +| "Maximum / Minimum Sum" | Any numbers | **Kadane's Algorithm** (DP) | +| "Subarray Sum + Frequent Updates" | Element mutations | **Fenwick Tree / Segment Tree** | +| "Subarray product = K" | No zeros | **Prefix Product** (division) | +| "Subarray product positive/negative" | Any numbers | **Parity tracking** of negative count | +| "Subarray XOR = K" | Any numbers | **Prefix XOR + Hash Map** | +| "Subarray OR / AND" | Any numbers | **Set of results** (bounded by 32 changes) | + +* Subarray Sum — Modular Arithmetic Insight [algorithm:interview] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Why does "subarray sum divisible by K" work with prefix remainders? Prove it. + +** Back +If Sum(i..j) mod K = 0, then: + (Prefix[j] - Prefix[i-1]) mod K = 0 + +By modular arithmetic: + Prefix[j] mod K = Prefix[i-1] mod K + +**Proof:** + Prefix[j] - Prefix[i-1] = m * K (for some integer m) + Prefix[j] = Prefix[i-1] + m * K + Prefix[j] mod K = (Prefix[i-1] + m * K) mod K + Prefix[j] mod K = Prefix[i-1] mod K (since m*K mod K = 0) + +So we just need to find pairs of indices with the same prefix remainder. + +**Caveat:** In C++/Java, % can return negative values for negative operands. Fix: + remainder = ((prefix_sum % K) + K) % K + +In Python, % always returns non-negative, so no fix needed. + +* Subarray Sum — Prefix State Generalization [algorithm:concept] +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What is the "prefix state" generalization, and how does it extend beyond addition? + +** Back +The core philosophy of prefix sums is: **accumulate history as you traverse linearly, and use a hash map to track state.** + +This generalizes far beyond addition: + +| Problem | Mapping | Reduces To | +|---------|---------|-----------| +| Equal 0s and 1s | 0→-1, 1→+1 | Subarray sum = 0 | +| Equal odd/even | even→+1, odd→-1 | Subarray sum = 0 | +| Equal vowels/consonants | vowel→+1, consonant→-1 | Subarray sum = 0 | +| Equal A/B/C counts | Track (c_A-c_B, c_B-c_C) | Prefix state tuple repeats | +| Subarray product | Prefix product | Division (handle zeros) | +| Subarray XOR | Prefix XOR | XOR is invertible | +| Subarray sum | Prefix sum | Subtraction | + +The pattern: +1. Define a state that accumulates as you traverse +2. Find a way to "undo" or compare states (subtract, XOR, divide, compare tuples) +3. Use a hash map to find when the same state (or target difference) appears