A sliding window breaks the moment a negative number shows up. This pattern replaces it. Store every running total, then let a Dictionary find the pair you need in one pass.
Pattern 1 ends with a warning. A window only works while the total grows as the window widens. One negative value breaks that. Prefix sums need no such rule. They turn “the sum of a range” into one subtraction. A hash map turns “find the range I want” into one lookup.
P[0] = 0 and P[i] = nums[0] + … + nums[i-1]. Then the sum of any range is one subtraction: sum(nums[i..j]) = P[j+1] - P[i]. A range query that cost O(n) now costs O(1), after one O(n) build.Reading the figure. The top row is nums. The bottom row is P, which starts with an extra 0. The blue cells are the range we want. Subtract the amber P[1] from the green P[4] to get its sum. The negative value changes nothing.
The subtraction alone still leaves O(n²) pairs to check. The second half of the pattern removes that.
P[j+1] - P[i] == k. Rearrange it: P[i] == P[j+1] - k. So walk the array once and keep a running P. At each step, ask the map how many earlier prefixes had the value P - k. Each one is a valid subarray ending here. One pass, O(n).This is the same move that turns Two Sum from O(n²) into O(n). Say so out loud. It shows you see one shared idea, not two memorised tricks.
[0] = 1. That entry stands for the empty prefix, the sum before any element. Without it you miss every subarray that starts at index 0. This one line is the most common bug in the pattern. It stays hidden until a test happens to need it.defaultdict. Read with GetValueOrDefault(key), which returns 0 for a missing int key.CollectionsMarshal.GetValueRefOrAddDefault. It hands back a ref to the slot. A plain map[key] = map.GetValueOrDefault(key) + 1 is fine too, and easier to read.int is 32 bits. A running sum of many large values can overflow. Use long for prefix sums when the constraints allow big totals.% keeps the sign of the left side. So -7 % 6 is -1, not 5. Divisibility problems must fix that.public static class PrefixArray
{
/// <summary>Builds P where P[i] is the sum of the first i elements.</summary>
/// <param name="nums">The input values. Any sign.</param>
/// <returns>An array one longer than nums. P[0] is 0.</returns>
/// <example>Build([3, -1, 4]) returns [0, 3, 2, 6].</example>
public static long[] Build(int[] nums)
{
// +1 so index n exists: P[0] is the empty sum, P[n] is the whole array.
// long, not int: a total of many big ints can pass int.MaxValue.
var prefix = new long[nums.Length + 1];
// i is the index of the next value to add.
// Invariant: prefix[i] already holds the sum of nums[0..i-1].
for (int i = 0; i < nums.Length; i++)
{
// i + 1: slot i + 1 covers one more element than slot i.
prefix[i + 1] = prefix[i] + nums[i];
}
return prefix;
}
/// <summary>Sum of nums[i..j] inclusive, in O(1).</summary>
/// <param name="prefix">An array made by Build.</param>
/// <param name="i">First index of the range.</param>
/// <param name="j">Last index of the range, inclusive.</param>
/// <returns>The range sum.</returns>
/// <example>RangeSum(Build([3, -1, 4, 2, 5]), 1, 3) returns 5.</example>
public static long RangeSum(long[] prefix, int i, int j)
{
// prefix[j + 1] sums nums[0..j]. +1 because P is shifted by the leading 0.
// Subtract prefix[i], the sum of nums[0..i-1], to leave nums[i..j].
return prefix[j + 1] - prefix[i];
}
}
C# has no accumulate one-liner that adds the leading zero. Write the loop. It is four lines and it shows the off-by-one clearly. LINQ’s Aggregate returns one value, not the running totals.
using System.Runtime.InteropServices;
public static class RangeCounter
{
/// <summary>Counts subarrays whose sum equals k.</summary>
/// <param name="nums">The input values. Any sign.</param>
/// <param name="k">The exact target sum.</param>
/// <returns>How many contiguous ranges sum to k.</returns>
/// <example>Count([2, 1, -1, 3], 3) returns 3.</example>
public static int Count(int[] nums, int k)
{
// prefix value -> how many earlier prefixes had it.
// [0] = 1: the empty prefix (sum 0) is seen once before the array starts.
// Without it, ranges from index 0 are lost.
var seen = new Dictionary<int, int> { [0] = 1 };
int running = 0; // 0: sum of no elements yet
int count = 0; // 0: no ranges found yet
// Invariant: running is the sum of nums up to and including value.
// seen holds every prefix that ends BEFORE value.
foreach (int value in nums)
{
running += value;
// A range ends here with sum k exactly when an earlier prefix
// equals running - k. Look up BEFORE recording this prefix,
// so the empty range is never counted. Missing keys read as 0.
count += seen.GetValueOrDefault(running - k);
// One hash lookup: get a ref to the slot, adding 0 if it is new.
ref int slot = ref CollectionsMarshal.GetValueRefOrAddDefault(
seen, running, out _);
slot++; // +1: this prefix is now seen one more time
}
return count;
}
}
Look up before you record. Recording first would let a prefix pair with itself, which is an empty subarray. Do not add to the dictionary while you hold the ref from GetValueRefOrAddDefault. A resize would leave the ref pointing at old memory.
Reading the figure. The top row is nums. The bottom row is the running prefix P. Each arc goes from the current prefix back to an earlier one that is exactly k smaller. Violet is the match found at P = 3. Green is the two matches found at P = 5. The value 2 shows up twice in P, so the map must store a count, not a flag.
public static class CombineAround
{
/// <summary>For each index, adds everything to its left and everything to its right.
/// </summary>
/// <param name="nums">The input values.</param>
/// <returns>answer[i] is the sum of all values except nums[i].</returns>
/// <example>EachExceptSelf([1, 2, 3, 4]) returns [9, 8, 7, 6].</example>
public static int[] EachExceptSelf(int[] nums)
{
// combine: the operation that merges values. Here it is +.
// Product of Array Except Self uses * with a start value of 1.
int n = nums.Length;
var answer = new int[n]; // new int[] starts at 0, the identity for +
// running starts at 0, the identity for +. It means "nothing to the left".
int running = 0;
// Left to right. i is the current index.
// Invariant: running holds the combine of nums[0..i-1], strictly left of i.
for (int i = 0; i < n; i++)
{
answer[i] = running; // store the left part before nums[i] joins it
running += nums[i]; // now running covers nums[0..i]
}
running = 0; // reset to 0: nothing to the right yet
// Right to left: start at n - 1 (last index), stop after 0.
// i >= 0 so index 0 is included. i-- walks backwards.
// Invariant: running holds the combine of nums[i+1..n-1], strictly right of i.
for (int i = n - 1; i >= 0; i--)
{
answer[i] += running; // merge the right part into the left part
running += nums[i]; // now running covers nums[i..n-1]
}
return answer;
}
}
Two sweeps in opposite directions. The answer array doubles as the scratch space. That is how you get O(1) extra space on “everything except me” problems.
Reading the figure. Each column is one index. The amber cell is the index in focus. Pass 1 walks right and stores the total of everything to the left (blue). Pass 2 walks left and holds the total of everything to the right (violet). The green cell adds the two. Read any column top to bottom to get its answer.
[0] = 1 seed. Every subarray starting at index 0 vanishes.k is 0.P has n + 1 entries, and sum(i..j) is P[j+1] - P[i]. Write that line down before coding.seen[key] throws KeyNotFoundException. Use GetValueOrDefault or TryGetValue.% on negatives. In C# the result can be negative. Normalise with ((x % k) + k) % k.Given an array of integers and a target k, return the number of contiguous subarrays whose sum equals k. Values may be negative.
running - k. That count is the number of subarrays ending here with sum k.public static class SubarraySum
{
/// <summary>Counts contiguous subarrays of nums summing to exactly k.</summary>
/// <param name="nums">Integers. Negative values are allowed.</param>
/// <param name="k">The exact target sum.</param>
/// <returns>The number of qualifying subarrays.</returns>
/// <example>Count([1, 2, 3], 3) returns 2.</example>
public static int Count(int[] nums, int k)
{
// prefix value -> how many times it has been seen so far.
// The empty prefix has sum 0 and is seen 1 time before any element.
// It lets a subarray that starts at index 0 be counted.
var seen = new Dictionary<int, int> { [0] = 1 };
int running = 0; // 0: sum of no elements yet
int count = 0; // 0: no subarrays found yet
// Invariant: running = sum of nums up to and including value.
// seen holds all prefixes that end before value.
foreach (int value in nums)
{
running += value;
// Every earlier prefix equal to (running - k) marks the start of a
// subarray ending here whose sum is exactly k. Missing key reads 0.
count += seen.GetValueOrDefault(running - k);
// Record this prefix AFTER the lookup. +1: seen once more.
seen[running] = seen.GetValueOrDefault(running) + 1;
}
return count;
}
}
nums = [1, 2, 3], k = 3:
running = 1. Look up 1 − 3 = −2. Found 0 times, so count = 0. seen is now {0:1, 1:1}.running = 3. Look up 3 − 3 = 0. Found 1 time, so count = 1. seen is now {0:1, 1:1, 3:1}.running = 6. Look up 6 − 3 = 3. Found 1 time, so count = 2. seen is now {0:1, 1:1, 3:1, 6:1}.The two subarrays are [1, 2] and [3]. Notice that the seed [0] = 1 is what found [1, 2].
0:1 finds [1, 2], and the stored 3:1 finds [3].Reading the figure. Each box is one turn of the loop. The first lines show the lookup for running - k. The row of small cells is the seen map after the step. Green marks the key the lookup found. Amber marks the new prefix just recorded. Notice the lookup always happens before the amber key is added.
public static class LongestSubarraySum
{
/// <summary>Length of the LONGEST subarray summing to k.
/// Stores the first index of each prefix, not a count.</summary>
/// <param name="nums">Integers. Negative values are allowed.</param>
/// <param name="k">The exact target sum.</param>
/// <returns>The longest length, or 0 if no subarray qualifies.</returns>
/// <example>Longest([1, -1, 5, -2, 3], 3) returns 4.</example>
public static int Longest(int[] nums, int k)
{
// prefix sum -> first index where it appeared.
// [0] = -1: the empty prefix (sum 0) "ends" at index -1, before the array.
// So a match at index i gives length i - (-1) = i + 1, the whole prefix.
var firstIndex = new Dictionary<int, int> { [0] = -1 };
int running = 0; // 0: sum of no elements yet
int best = 0; // 0: no qualifying subarray found yet
// index is the current end. Invariant: running = sum of nums[0..index].
for (int index = 0; index < nums.Length; index++)
{
running += nums[index];
// An earlier prefix of running - k means nums[start+1..index] sums to k.
if (firstIndex.TryGetValue(running - k, out int start))
{
// Length = index - start. No +1: start is the index BEFORE the range.
best = Math.Max(best, index - start);
}
// Only record the FIRST time a prefix appears: earlier start, longer span.
// TryAdd does nothing if the key already exists.
firstIndex.TryAdd(running, index);
}
return best;
}
}
TryAdd says that in one call. Overwriting makes the answer too small, and it passes most small tests.k == 0 with zeros in the array, such as [0, 0, 0]: the answer is 6. Good test of the look-up-then-record order.k: works unchanged.0.0.int can overflow. Switch running and the key type to long.P[j] - P[i] == k into P[i] == P[j] - k. That is a dictionary lookup, exactly like Two Sum.”Return true if the array has a contiguous subarray of length at least two whose sum is a multiple of k. k is a positive integer, and a multiple includes 0 × k.
P[j] - P[i] is a multiple of k exactly when P[j] % k == P[i] % k. So stop storing sums and store remainders. The question becomes: have I seen this remainder before, far enough back? Two equal remainders bracket a valid subarray.There are only k possible remainders. So on a long array a repeat is certain. The whole difficulty is the length-at-least-two rule.
[0] = -1. The empty prefix has remainder 0 and sits before index 0. That makes the length math work for a subarray that starts at the beginning.firstIndex[remainder] + 1 to index. So its length is index - firstIndex[remainder]. Require at least 2.public static class ContinuousSubarraySum
{
/// <summary>True if some subarray of length 2 or more sums to a multiple of k.
/// </summary>
/// <param name="nums">Integers. The usual statement says non-negative.</param>
/// <param name="k">A positive divisor.</param>
/// <returns>True if such a subarray exists.</returns>
/// <example>Check([23, 2, 4, 6, 7], 6) returns true.</example>
public static bool Check(int[] nums, int k)
{
// remainder -> earliest index where the running sum had it.
// [0] = -1: the empty prefix has remainder 0 and ends at index -1.
// It lets a range that starts at index 0 qualify.
var firstIndex = new Dictionary<int, int> { [0] = -1 };
int running = 0; // 0: remainder of the empty sum
// index is the current end.
// Invariant: running = (sum of nums[0..index]) mod k, in 0..k-1.
for (int index = 0; index < nums.Length; index++)
{
// % k: two prefixes with the same remainder differ by a multiple of k.
// Reducing each step keeps running small, so it cannot overflow.
// + k then % k again: C# % keeps the sign, so this lifts a
// negative remainder into 0..k-1. Harmless for non-negative input.
running = ((running + nums[index]) % k + k) % k;
if (firstIndex.TryGetValue(running, out int start))
{
// The range is start + 1 .. index, so its length is the gap.
// >= 2: the problem needs at least two elements.
if (index - start >= 2) return true;
}
else
{
firstIndex[running] = index; // earliest only, never overwrite
}
}
return false;
}
}
nums = [23, 2, 4, 6, 7], k = 6:
true.The subarray is [2, 4], which sums to 6.
k.Reading the figure. The rows show nums, the running sum, and the running sum mod 6. The two green cells share remainder 5. The arc joins them. The amber cells between them are the subarray [2, 4], and its sum is 6. The loop returns at index 2, so the dashed cells are never computed.
The map holds at most k distinct remainders. That bound is tighter than O(n), and it is worth stating.
% follows the left side. So -7 % 6 is -1, while Python gives 5. A remainder of -1 and a remainder of 5 mean the same class, but the dictionary sees two keys. Add k and take % k again. Say this. It is a free point. Math.DivRem has the same sign rule, so it does not help.[0] = -1 and not [0] = 0. Take [6, 1] with k = 6. At index 0 the remainder is 0. It is already in the map at -1, and 0 - (-1) = 1, which is too short. That is correct: a single 6 does not qualify. With a seed of [0] = 0, the math is off by one and the length test misfires.false.[0, 0] with any k: true, since 0 is a multiple of everything.[1, 0] with k = 2: false. A good check of the length rule.k: does not qualify, because of the length rule.% can go negative.”Return an array where answer[i] is the product of every element except nums[i]. Solve it without division and in O(n) time.
public static class ProductExceptSelf
{
/// <summary>Product of all elements except the one at each index, without division.
/// </summary>
/// <param name="nums">Integers. Zeros are allowed.</param>
/// <returns>A new array where answer[i] is the product of every other element.
/// </returns>
/// <example>Product([1, 2, 3, 4]) returns [24, 12, 8, 6].</example>
public static int[] Product(int[] nums)
{
int n = nums.Length;
var answer = new int[n];
// Pass 1, left to right: answer[i] becomes the product of everything
// strictly to the left of i.
int prefix = 1; // 1: empty product, nothing left of index 0
// Invariant: prefix = product of nums[0..i-1] at the top of each pass.
for (int i = 0; i < n; i++)
{
answer[i] = prefix; // store the left product before nums[i] joins
prefix *= nums[i]; // now prefix covers nums[0..i]
}
// Pass 2, right to left: multiply in the product of everything
// strictly to the right of i.
int suffix = 1; // 1: empty product, nothing right of n - 1
// Start at n - 1, the last index. i >= 0 so index 0 is included.
// i-- walks backwards.
// Invariant: suffix = product of nums[i+1..n-1] at the top of each pass.
for (int i = n - 1; i >= 0; i--)
{
answer[i] *= suffix; // left product times right product
suffix *= nums[i]; // now suffix covers nums[i..n-1]
}
return answer;
}
}
nums = [1, 2, 3, 4]. Each line shows the left product after pass 1, the suffix at that index, and the final value:
Reading the figure. Each column is one index. Pass 1 writes the product of everything to the left (blue). Pass 2 walks back and multiplies in the product of everything to the right (violet). The green cell is the result. Both passes start at 1, so the ends of each row are 1. No division is used anywhere.
By the usual rule, the output array does not count as extra space. Say so. The problem statement usually says so too.
[1, 2, 0, 4]. The correct answer is [0, 0, 8, 0]. Division by the total divides by zero, and C# throws DivideByZeroException for int.[0, 0, 3]. The correct answer is [0, 0, 0]. The total is 0, so every entry needs a special case.You can patch division by counting zeros and branching on 0, 1, or more. It works, and it is ugly. The two-pass version needs no cases at all. That is the real argument for it.
[1], the empty product. Confirm that is wanted.int product overflows and wraps without warning. Use long, or wrap the multiply in checked to throw OverflowException instead. Python hides this. C# does not.Return the number of contiguous subarrays whose sum is divisible by k. Values may be negative.
k as you go. The keys stay in 0..k-1, so the counts fit in an int[k]. No dictionary is needed.public static class SubarraysDivByK
{
/// <summary>Counts contiguous subarrays whose sum is divisible by k.</summary>
/// <param name="nums">Integers. Negative values are allowed.</param>
/// <param name="k">A positive divisor.</param>
/// <returns>The number of qualifying subarrays.</returns>
/// <example>Count([4, 5, 0, -2, -3, 1], 5) returns 7.</example>
public static int Count(int[] nums, int k)
{
// remainder -> how many earlier prefixes had it.
// Keys are 0..k-1, so an array of size k replaces the dictionary.
var counts = new int[k];
counts[0] = 1; // the empty prefix has remainder 0, seen once
int running = 0; // 0: remainder of the empty sum
int total = 0; // 0: no subarrays found yet
// Invariant: running = (sum of nums up to and including value) mod k.
foreach (int value in nums)
{
// % k keeps the key small. C# % can return a negative number,
// so + k then % k again moves it into 0..k-1.
running = ((running + value) % k + k) % k;
// Each earlier prefix with this remainder closes one valid subarray.
total += counts[running];
counts[running]++; // +1: record this prefix after the lookup
}
return total;
}
}
nums = [4, 5, 0, -2, -3, 1], k = 5. Each line shows the remainder, the count for it before the step, and the new total:
% gives -1, and the fix turns it into 4.Answer 7. A remainder seen three times adds three subarrays at its fourth appearance. That is the counting half doing its job.
k.Reading the figure. The bottom row is every prefix sum mod 5, starting with the seed 0. Cells with the same color hold the same remainder. Any two cells of one color bracket a subarray whose sum divides by 5. So the answer is the number of same-color pairs: 6 amber pairs plus 1 green pair.
m times in the prefix array, counting the seeded empty prefix. It then yields m × (m-1) / 2 subarrays, one for each pair. Adding counts[running] before the increment builds that sum step by step: 0, then 1, then 2, and so on. Naming the closed form shows you see the structure, not just the loop.k, such as [5, 5, 5] with k = 5: every subarray qualifies, so the answer is 6.+ k fix. Without it, counts[-1] throws IndexOutOfRangeException. That crash is a gift: it tells you the bug at once.k == 1: everything is divisible, so the answer is n(n+1)/2.0.int.MaxValue near n = 65,000. Return long if n can be that big.sum(nums[i..j]) = P[j+1] - P[i]. The prefix array is one longer than the input, and that leading zero matters.P[j] - P[i] == k into P[i] == P[j] - k. That is a hash lookup, the same move as Two Sum.[0] = 1 for counts, or [0] = -1 for indices.TryAdd does exactly that.k entries, so an int[k] works.% can be negative, and int can overflow. Normalise with ((x % k) + k) % k and reach for long.Pattern 14, Monotonic Stack, answers a different range question. It does not ask for the aggregate of a range. It asks where the range ends. For every element, what is the next larger one, and how far away is it?