A battery-dispatch DP indexed only by interval gives wrong answers — which state dimension is missing?
answer
- what does one cell remember?
- two plans, same interval, different futures
- can the transition tell charged from empty?
- the cooldown rule names a dimension
- empty, holding, cooling
basics
~20 sThe state forgets whether the battery currently holds a charge and whether it is inside the mandatory idle window. Add that dimension — one cell per interval per situation (empty, holding, cooling) — because two plans reaching the same interval have different legal futures and must not share a cell.
solid answer
~50 sA one-dimensional table says "the best value after interval `i`", but that is not a sufficient summary of the past: a plan that arrives at interval `i` holding a charge can discharge next, and a plan that arrives having just discharged is forbidden from charging next. The index alone merges those histories into one cell, so the transition has to guess, and the sketch ends up modelling only charge-then-discharge pairs — it silently prices plans that hold across several intervals and never enforces the idle rule. The fix is a second dimension whose values are the distinguishable situations: `dp[i][empty]`, `dp[i][holding]`, `dp[i][cooling]`. The design test is general — **if two prefixes reaching the same index have different sets of legal continuations, the index alone is not a state.** The bug is nasty because a one-interval or two-interval example still comes out right.
code
pseudocode · 8 lines// c[i] = cost to charge during interval i, r[i] = revenue to discharge during i
// battery holds at most one charge; one idle interval is required after discharging
best[0] = 0
best[1] = 0
for i in 2..n
best[i] = best[i-1] // idle through interval i
best[i] = max(best[i], best[i-2] - c[i-1] + r[i]) // charge at i-1, discharge at i
answer = best[n]go deeper
Be ready to notice when a problem rule refers to something that happened earlier, such as a cooldown or a carried resource. That is a signal the position index alone cannot be the whole state.
Explain the sufficiency test out loud: two histories arriving at the same index with different legal next moves cannot share a cell. Then write one transition line per situation and read each as a sentence.
Show that you catch this in review and in testing, not by luck. Argue for inputs shaped to make the rule bind, since small random cases are precisely where a merged state still agrees with the truth.
Own the modelling vocabulary: situations named in the problem's own language, written next to the table, so a later change to the rules maps to a state change rather than to another branch bolted onto a transition.
## What a DP state actually has to be A state is a **summary of the past that makes the future independent of how you got there**. Once you are in a state, the set of legal next actions and their consequences must be fully determined by that state and the remaining input — never by history the state has thrown away. Every missing-dimension bug is a violation of exactly that sentence. Take a battery-dispatch planner over a day sliced into intervals `1..n`. During any interval you may charge (paying `c[i]`), discharge (earning `r[i]`), or idle. The battery holds at most one charge, and after discharging, the hardware requires one full idle interval before charging again. Maximise total value. The attractive first draft indexes only by interval: ``` best[i] = max(best[i-1], best[i-2] - c[i-1] + r[i]) ``` Read it carefully and the modelling error is visible. The second term is the only one that ever earns anything, and it hard-codes a single shape: charge in the interval immediately before discharging. A plan that charges cheaply in the morning and holds until the evening peak is not expressible. Worse, the `best[i-2]` term does not actually enforce the cooldown, because the plan summarised by `best[i-2]` may itself have ended with a discharge at interval `i-2`, and charging at `i-1` right after it is illegal. Both failures have one cause: **the cell `best[i]` merges two histories with different futures.** One reached interval `i` empty and free to charge; another reached it holding a charge; another just discharged and must idle. They are not interchangeable, so they cannot share a cell. ## Adding the dimension Enumerate the situations the rules actually distinguish, and give each a name: - `empty` — no charge stored, free to act. - `holding` — a charge is stored, waiting to be sold. - `cooling` — discharged during this interval, so the next interval must be idle. That yields `dp[i][s]` with three values of `s` and one transition per legal move: ``` dp[i][empty] = max(dp[i-1][empty], dp[i-1][cooling]) dp[i][holding] = max(dp[i-1][holding], dp[i-1][empty] - c[i]) dp[i][cooling] = dp[i-1][holding] + r[i] answer = max(dp[n][empty], dp[n][cooling]) ``` Each line reads as a sentence. You are empty at `i` if you were empty and idled, or you were cooling at `i-1` and have now served the mandatory idle interval — which is precisely why a charge cannot follow a discharge immediately, since `holding` at `i` is only reachable from `empty` at `i-1`. Base case: `dp[0][empty] = 0`, the other two seeded with a sentinel so unreachable situations never win a maximum. The state count went from `n` to `3n`, transitions stay constant-work, and the model is now correct for plans of any shape — including holding a charge across dozens of intervals, which the one-dimensional sketch could not represent at all. ## Why this bug survives testing On a two- or three-interval example, charge-immediately-then-discharge *is* the optimal plan, so the broken table agrees with hand calculation and with brute force. The disagreement only appears when the profitable plan requires holding across intervals or when the cooldown genuinely binds. Random tiny inputs are therefore the worst possible test for this class of defect: they are exactly the inputs on which the missing dimension does not matter. Generate inputs shaped to force the rule — a cheap early interval and an expensive late one, or two attractive discharges back to back — and compare against exhaustive enumeration. ## The two directions of state-design error Missing dimensions and surplus dimensions fail differently, and the difference is worth naming: - **Too few dimensions** merges histories with different futures. The symptom is a *wrong answer* that looks plausible and often passes small cases. This is the dangerous one, because the code runs. - **Too many dimensions** splits identical subproblems into duplicates. The symptom is *cost*: more cells, more memory, more time, and often a table that is mostly unreachable. The answers stay correct. This asymmetry is why "when unsure, add a dimension" is bad advice dressed as caution. It is not free, and it does not diagnose anything — a dimension added without a stated meaning just hides the modelling question you have not answered yet. ## Recognising it in an interview When a rule in the problem statement refers to *what happened before* — a cooldown, a limit on consecutive actions, a resource you are carrying, a mode you are in — that rule is naming a dimension. Say so out loud: "the rules distinguish three situations at each interval, so my state is interval plus situation". Then write one transition line per situation and read each aloud as a sentence. A transition you cannot say in words is a transition you have not verified.
- How would you decide how many situation values the second dimension needs?List the situations the rules can distinguish, then merge any two whose sets of legal next actions and future consequences are identical — indistinguishable futures mean one state. Here charging, holding for one interval and holding for ten all behave the same going forward, so they collapse to `holding`; just-discharged behaves differently, so `cooling` stays separate.
- Why not encode "holding" by storing the interval in which the charge was bought?It would be correct but wasteful. The transitions only need the fact that a charge is stored, not when it was acquired, so keying on the purchase interval splits one state into up to `n` copies with identical futures. That is a surplus dimension: same answers, a factor of `n` more cells and work.
- A new rule caps the number of discharge cycles per day. What happens to the state?It adds a genuine third component — cycles used so far — because nothing else in the state determines it and the transitions must read it to know whether discharging is still allowed. States become interval by situation by cycles-used, which multiplies the table by the cap. Unlike the purchase interval, this component is not derivable from the rest.
- How would you test for a missing dimension before you are confident in the model?Compare against exhaustive enumeration on inputs designed to make the rule bind, not on random tiny ones. Small random inputs are where the merged histories happen to agree, which is why the bug survives them. Construct a cheap early interval with an expensive late one to force holding, and two attractive back-to-back discharges to force the cooldown.
A parking meter that records only the time forgets whether a car is parked; the same reading means two different legal next moves.
saying these in an interview costs you the question
- Claims the one-dimensional table is fine because small cases pass
- Patches the recurrence with extra branches instead of a state dimension
- Adds a dimension without saying what its values mean
- Believes the index alone always summarises the past
- Thinks the bug will show up as a crash rather than a wrong number