How does a running total plus a map of seen totals count zero-net stretches in a signed ledger?
answer
- The stretch total is a difference of two totals
- Equal running totals bracket something
- What must be stored per total, not just whether
- Seed the map before the first day
- Query the map, then record today
basics
~20 sKeep a running total of the signed deltas and a map counting how often each running total has already appeared. Two positions with the same running total bracket a stretch that nets to zero, so each earlier match adds one more qualifying stretch.
solid answer
~40 sWalk the ledger once, maintaining `sum`, the total of everything seen so far. The total of the stretch between two positions is the difference of their running totals, so a stretch nets to zero exactly when the running total at its end equals the running total just before its start. That turns the problem into "how many earlier positions had this same running total", which a map from running total to occurrence count answers in expected constant time. At each day: update `sum`, add `seen[sum]` to the answer, then increment `seen[sum]`. Seed the map with the empty prefix (total 0 seen once) so stretches beginning on day one are counted. One pass, O(n) time, O(n) space in the worst case, and it handles negative deltas and zeros without special cases.
go deeper
Be ready to state the identity out loud: the total of a stretch is the difference of two running totals, so equal running totals mean the span between them nets to zero. Then describe the single pass in three steps.
Explain why the map stores counts rather than a seen-flag, and why the lookup happens before the insert. Give honest costs: one pass, expected constant-time map operations, O(n) memory in the worst case.
Show you know the failure surface: the empty-prefix seed, accumulating totals that can overflow a fixed-width counter on long inputs, and hash lookups degrading under pathological keys. Say when the memory cost makes a cheaper approach preferable.
Frame it as a choice: this pattern buys linear time with linear memory and one sequential read, which matters when the ledger is streamed and cannot be re-read. Be able to argue when a quadratic scan on small batches is the better engineering call.
## The scenario An audit walks a ledger of signed daily balance deltas — money in is positive, money out is negative — and asks how many contiguous billing periods net to exactly zero. Brute force checks every start and every end: O(n^2) additions, fine for a hundred days and hopeless for a million. ## The identity that makes it one pass Define the running total `P[i]` as the sum of the first `i` deltas, with `P[0] = 0` (the empty prefix, before any day). Then the total of the stretch covering days `l..r` is ``` sum(l..r) = P[r+1] - P[l] ``` A stretch nets to zero exactly when `P[r+1] == P[l]`. So the question "how many zero-net stretches are there" is really the question "how many PAIRS of positions share the same running total". Nothing about the individual deltas matters any more — only repeats of the running total. ## The algorithm Scan once. Maintain `sum` and a map `seen` from running total to how many times that total has occurred so far, seeded with `seen[0] = 1` for the empty prefix. 1. Add today's delta to `sum`. 2. Add `seen[sum]` to the answer — every earlier position holding this same total is the left edge of a zero-net stretch ending today. 3. Increment `seen[sum]`. The order matters: query first, then record. If you record first, today's position matches itself and you count an empty stretch. The same skeleton answers the general question "how many stretches total exactly `target`": look up `sum - target` instead of `sum`, because `P[l] = P[r+1] - target` is what a stretch of the required total demands. ## Why a count, not just a flag A running total can recur many times — a ledger that oscillates around the same balance produces it constantly — and each earlier occurrence is the left edge of a distinct qualifying stretch. A set that only remembers "this total appeared" undercounts badly: three earlier occurrences mean three stretches ending here, not one. Store the multiplicity. If instead you want the LONGEST zero-net stretch rather than how many there are, the bookkeeping flips: you keep the first index at which each total occurred and never overwrite it. Counting wants frequencies; longest wants earliest positions. ## Cost, stated precisely Time is O(n) map operations. Hash map lookup and insert are expected O(1) and amortized O(1) across growth, not O(1) in the worst case: adversarial or pathological key sets can collide and degrade a lookup toward O(n). For interview purposes the honest sentence is "expected linear time". Space is O(n) in the worst case — a strictly increasing balance never repeats a total, so every one of the n+1 running totals lands in the map. That is the real price of this pattern versus a two-pointer window, which is O(1) space but cannot cope with negative values. One more caution that only bites at scale: running totals accumulate, so on a long ledger of large magnitudes the totals themselves can grow beyond the range of a fixed-width integer even when every individual delta is small. The differences are what you care about, but the keys you store are the raw accumulations. ## What the pattern does NOT need It needs no sorting (sorting destroys contiguity, which is the whole point), no window, no assumption that deltas are positive, and no second pass. It does need random-access-free sequential reading only, which makes it a natural fit for streaming a ledger once. ## The common wrong answers "Two equal running totals mean the days between them were all zero" — no, they mean the days between them CANCEL; a +40 followed by a -40 qualifies. "Use a sliding window" — that assumes growing the window cannot shrink the sum, which negative deltas violate. "Sort the deltas first" — sorting reorders days, and a stretch must be contiguous.
- How does the same one-pass skeleton count stretches totalling some non-zero target instead of zero?Change the lookup key. A stretch ending here totals `target` exactly when the running total just before it equals `sum - target`, so you add `seen[sum - target]` to the answer instead of `seen[sum]`, then record `seen[sum]` as before. The seed stays the same: total 0 seen once, representing the empty prefix, which is what makes stretches starting at the first element countable.
- Why store an occurrence count per running total rather than just marking each total as seen?Because a total can recur many times, and every earlier occurrence is the left edge of a different qualifying stretch ending at the current position. A ledger that returns to the same balance five times contributes five stretches at the sixth visit, not one. A set-based version reports each end position at most once and systematically undercounts on oscillating data.
- What is the worst-case space cost, and when do you actually hit it?O(n): the map holds one entry per distinct running total, and a ledger whose balance only ever climbs produces n+1 distinct totals with no repeats at all. That is the paradox of the pattern — the input with zero answers costs the most memory. If the totals are bounded to a small range, the map stays small regardless of n.
Two mile markers showing the same odometer reading mean the road between them was a loop: you moved, but you ended where you started.
saying these in an interview costs you the question
- Says equal running totals mean the values between them are zero
- Uses a set of seen totals instead of counts, undercounting repeats
- Forgets the empty prefix, missing stretches starting at index 0
- Records the current total before querying, counting an empty stretch
- Claims map lookups are O(1) worst-case rather than expected