How do you count contiguous runs whose total is divisible by k using prefix totals?
answer
- Divisible means the two ends agree modulo k
- The key stops being the total itself
- Only k distinct keys can ever exist
- Seed the remainder-zero bucket
- Negative entries can split one class in two
basics
~20 sBucket running totals by their remainder modulo k instead of by their exact value. Two positions in the same remainder bucket bracket a run whose total divides evenly by k, so count pairs per bucket, seeding remainder 0 with one for the empty prefix.
solid answer
~50 sThe identity is the same as for an exact target, taken modulo k: the run between two positions totals `P[r+1] - P[l]`, and that difference is a multiple of k exactly when the two running totals are congruent mod k. So the map key stops being the running total and becomes its remainder class — at most k distinct keys, so O(min(n, k)) space rather than O(n). Scan once, add the bucket's current count to the answer, then increment it; seed the remainder-0 bucket with one for the empty prefix. The portability trap is the remainder itself: where the entries can be negative, a remainder operation may hand back a negative value, splitting one congruence class across two keys so genuinely matching positions never meet. Normalise every key as `((x mod k) + k) mod k` before touching the map.
go deeper
Recall that divisibility by k depends only on remainders: if two running totals leave the same remainder when divided by k, the run between them has a total that is a multiple of k.
Explain the key change from exact total to remainder class, why that caps memory at k buckets, and why the remainder-zero bucket must be seeded for runs starting at the first entry.
Show you would normalise every key with ((x mod k) + k) mod k unconditionally, and explain why the resulting undercount is invisible to a positive-only test set. Mention guarding k = 0 and keeping the accumulator bounded.
Own the portability angle: behaviour that depends on an environment's remainder convention is a latent defect when the same logic is reimplemented in another stack. Argue for normalising at the boundary and for test data that deliberately carries negatives.
## The setup A warehouse ledger records signed weight adjustments per shipment line — dispatches positive, returns negative — and you want to count contiguous runs whose net weight is an exact multiple of the pallet size `k`, because those runs load cleanly. ## From exact totals to remainder classes Start from the identity: the run covering entries `l..r` totals `P[r+1] - P[l]`, where `P` is the running total. That difference is divisible by k exactly when ``` P[r+1] mod k == P[l] mod k ``` So instead of asking "which earlier positions had this exact total", you ask "which earlier positions had this REMAINDER". The map key changes; nothing else does. The scan is the familiar one. Keep a running total, reduce it mod k, add the bucket's current count to the answer, then increment the bucket. Seed the remainder-0 bucket with a count of one — the empty prefix has total 0, remainder 0, and is what makes runs starting at the first entry countable. ## What the key change buys Space drops from O(n) to O(min(n, k)): there are only k possible remainders, so a pallet size of 8 means eight buckets no matter how many million ledger lines you stream. When k is small and known, an indexed table of k counters replaces the hash map outright — no hashing, contiguous memory, and the same asymptotics with a much better constant. It also hands you a free existence proof. There are n+1 running totals including the empty prefix, dropped into k buckets, so as soon as n >= k the pigeonhole principle guarantees at least two share a bucket — meaning any ledger of at least k entries CONTAINS a run divisible by k. Interviewers like that observation because it shows you reasoned about the structure rather than only coding the scan. ## The remainder trap, which is the real question Remainder operations do not agree on sign across environments. Some define the result to follow the sign of the dividend, so reducing a negative running total yields a negative remainder; others always return a value in `[0, k)`. Under the sign-following rule, a running total of -2 with k = 5 keys the bucket `-2`, while a running total of +3 — congruent to it, since -2 and 3 differ by 5 — keys the bucket `3`. One congruence class, two buckets, and the pairs that should have matched never do. The output is an undercount that appears only when the data contains negatives, which is exactly the case a positive-only test set never exercises. The fix is one expression applied to every key before it reaches the map: ``` key = ((sum mod k) + k) mod k ``` The first reduction lands in `(-k, k)`, adding k lifts it non-negative, and the second reduction brings it back into `[0, k)`. Applying it unconditionally costs nothing and removes the environment dependence entirely, which is why it belongs in the code rather than in a comment about which behaviour you assume. ## Edge cases to state - k = 0 is not a valid pallet size; guard it, since a remainder by zero is undefined. - Negative k should be normalised to its absolute value before bucketing; divisibility does not care about the sign of the divisor, but bucket indices do. - The running total can still overflow a fixed-width accumulator on a long ledger even though the KEYS stay small. Reducing the running total mod k as you go — rather than accumulating the raw total and reducing at the end — keeps the accumulator bounded and is worth mentioning. ## Counting versus longest, again The two bookkeeping styles carry over unchanged. To COUNT runs, store a frequency per remainder bucket and increment on every visit. To find the LONGEST run divisible by k, store the earliest index per remainder, write only when the bucket is empty, and seed remainder 0 with index -1. Same identity, same single pass, different value in the bucket. ## What a weak answer looks like "Check every run's total against k" — that is the quadratic scan the pattern replaces. "Use the running total itself as the key" — that finds runs totalling exactly zero, a strictly narrower question. "Remainders are always non-negative" — assuming that is exactly the defect this question exists to expose.
- Why does normalising the remainder matter only when the entries can be negative?Because remainder operations differ in sign convention: some return a value following the dividend's sign, so a negative running total produces a negative key. With only non-negative entries the running total never goes negative and the two conventions agree, which is why positive-only tests pass. Once negatives appear, one congruence class splits across two keys — say -2 and 3 for k = 5 — and matching positions stop meeting.
- How does the memory cost compare with the exact-total version of the same pattern?The exact-total version stores one entry per distinct running total, up to n+1 of them, so O(n) worst case. Bucketing by remainder caps the key space at k, giving O(min(n, k)). When k is small and known ahead of time, an indexed table of k counters replaces the map entirely, dropping hashing overhead and improving locality without changing the asymptotics.
- Can you say anything about whether such a run exists before scanning?Yes, by pigeonhole. There are n+1 running totals counting the empty prefix, and only k possible remainders, so once n >= k two of them must share a bucket and a qualifying run is guaranteed to exist. It does not tell you where, so you still scan, but it settles the existence question immediately and is a good sign you reasoned about structure.
- How would you adapt this to report the longest such run instead of the count?Store the earliest index at which each remainder appeared rather than a frequency, write a bucket only when it is empty so the earliest index survives, and seed remainder 0 with index -1 for the empty prefix. On each repeat, the candidate length is the current index minus the stored one. The identity and the single pass are unchanged; only the bucket's payload differs.
saying these in an interview costs you the question
- Keys the map by the running total instead of its remainder
- Assumes a remainder operation never returns a negative value
- Omits the seed for the remainder-zero bucket
- Claims space is O(n) when only k buckets can exist
- Recomputes each candidate run's total to test divisibility