If restarting cost nothing, the answer would be the cheapest hours. With a fixed cost for each restart, choices interact, so build a table: cost[j][on] is the cheapest way to have charged j hours so far, where on says whether the last hour was charging. Each hour updates every state in O(1), so it is O(H × need) time.
mediumPattern: 2-D dynamic programmingFrom Devana's Tesla question set
Problem
An electric car is plugged in overnight and has to be ready by a set time. Electricity is priced by the hour, and you know the price of every hour until departure. The car needs a whole number of hours of charging, and it does not need them in one block.
Each time charging starts, add a fixed start_cost; it models the real constraint that frequent start-stop cycles are undesirable. Write schedule_charging(prices, need, start_cost), which returns the lowest total cost, meaning the prices of the charged hours plus start_cost for each separate block, and the hours to charge.
def schedule_charging(prices: list[int], need: int, start_cost: int) -> dict | None:This is one of Devana's own questions, written in the style of Tesla's interviews. It is not a question leaked from Tesla, and Devana is not affiliated with Tesla.
Constraints
prices[i]is the price of hour i, from plug-in to departure, for up to 48 hours.needhours must be charged, each hour fully or not at all. ReturnNoneifneedis more than the number of hours.- Total cost is the sum of the charged hours' prices plus
start_costtimes the number of separate charging blocks. - Return
{"cost": total, "hours": sorted hour indexes}; any cheapest schedule is accepted.
Examples
Example 1
Input: prices = [5, 1, 1, 5, 1, 1], need = 4, start_cost = 0
Output: {"cost": 4, "hours": [1, 2, 4, 5]}With no restart cost, the four cheapest hours win, in two blocks.
Example 2
Input: prices = [5, 1, 1, 5, 1, 1], need = 4, start_cost = 5
Output: {"cost": 13, "hours": [1, 2, 3, 4]}Two blocks would cost 4 + 2 × 5 = 14. One block through the expensive hour costs 8 + 5 = 13, so it wins. Hours [2, 3, 4, 5] cost the same and are also accepted.
Hints
Open one at a time, and try the problem again after each.
Hint 1
With start_cost = 0, what is the answer? Why does a positive start_cost break that greedy choice?
Hint 2
Go hour by hour. What do you need to remember about the past to price the decision for the next hour?
Hint 3
Two things: how many hours you have charged so far, and whether the previous hour was charging, which decides whether charging now starts a new block.
Hint 4
With state cost[j][on]: staying idle keeps j and turns on off. Charging adds one to j and pays start_cost only if the previous state was off.
Approach: from brute force to optimal
Brute force: try every set of hours
Choose need of the H hours in every possible way, price each schedule, and keep the cheapest. With 24 of 48 hours needed, that is about 32 trillion schedules.
Complexity: O(C(H, need) × H)
Greedy: the cheapest hours
Sort hours by price and take the cheapest need. Optimal when start_cost is 0 and wrong otherwise, because it happily charges in many short blocks.
Complexity: O(H log H)
Dynamic programming over the hours
Process the hours in order with cost[j][on]. For each hour and each j, idling gives the new off state min(cost[j][0], cost[j][1]), and charging gives the new on state min(cost[j-1][0] + start_cost, cost[j-1][1]) + price.
Record which previous state each value came from. At the end, take the cheaper of cost[need][0] and cost[need][1] and walk the records back to list the charged hours.
Complexity: O(H × need) time; O(H × need) space for the back-pointers, O(need) for the costs alone
How to say it in the interview
Start with the greedy answer and the example where it fails; that earns the dynamic program. Then give the state in one sentence, "hours charged so far, and whether the charger is on", and the two transitions. Interviewers mostly want to hear that you chose the state deliberately.
Solution
Python
def schedule_charging(prices, need, start_cost):
if need > len(prices):
return None
INF = float("inf")
# cost[j][on]: cheapest way to have charged j hours so far; on says
# whether the most recent hour was a charging hour.
cost = [[INF, INF] for _ in range(need + 1)]
cost[0][0] = 0
came_from = [] # per hour: the previous on state of each state
for price in prices:
nxt = [[INF, INF] for _ in range(need + 1)]
back = [[0, 0] for _ in range(need + 1)]
for j in range(need + 1):
# Idle this hour.
prev_on = 1 if cost[j][1] < cost[j][0] else 0
nxt[j][0], back[j][0] = cost[j][prev_on], prev_on
# Charge this hour; starting a new block pays start_cost.
if j > 0:
fresh = cost[j - 1][0] + start_cost + price
keep_going = cost[j - 1][1] + price
if keep_going <= fresh:
nxt[j][1], back[j][1] = keep_going, 1
else:
nxt[j][1], back[j][1] = fresh, 0
cost = nxt
came_from.append(back)
on = 1 if cost[need][1] < cost[need][0] else 0
total, hours, j = cost[need][on], [], need
for hour in range(len(prices) - 1, -1, -1): # walk the choices back
prev_on = came_from[hour][j][on]
if on:
hours.append(hour)
j -= 1
on = prev_on
return {"cost": total, "hours": sorted(hours)}JavaScript
function scheduleCharging(prices, need, startCost) {
if (need > prices.length) return null;
// cost[j][on]: cheapest way to have charged j hours so far; on says
// whether the most recent hour was a charging hour.
let cost = Array.from({ length: need + 1 }, () => [Infinity, Infinity]);
cost[0][0] = 0;
const cameFrom = []; // per hour: the previous on state of each state
for (const price of prices) {
const next = Array.from({ length: need + 1 }, () => [Infinity, Infinity]);
const back = Array.from({ length: need + 1 }, () => [0, 0]);
for (let j = 0; j <= need; j++) {
// Idle this hour.
const prevOn = cost[j][1] < cost[j][0] ? 1 : 0;
next[j][0] = cost[j][prevOn];
back[j][0] = prevOn;
// Charge this hour; starting a new block pays startCost.
if (j > 0) {
const fresh = cost[j - 1][0] + startCost + price;
const keepGoing = cost[j - 1][1] + price;
next[j][1] = Math.min(fresh, keepGoing);
back[j][1] = keepGoing <= fresh ? 1 : 0;
}
}
cost = next;
cameFrom.push(back);
}
let on = cost[need][1] < cost[need][0] ? 1 : 0;
const total = cost[need][on];
const hours = [];
let j = need;
for (let hour = prices.length - 1; hour >= 0; hour--) { // walk the choices back
const prevOn = cameFrom[hour][j][on];
if (on) {
hours.push(hour);
j--;
}
on = prevOn;
}
return { cost: total, hours: hours.sort((a, b) => a - b) };
}Both versions are tested against the same cases, including every example on this page.
- Only the previous hour's row is needed for the costs, so they take O(need) memory. The back-pointers are what cost O(H × need), and they are only needed to list the hours.
- Prices are whole numbers here, so totals are exact. With fractional prices, work in the smallest currency unit or compare with a tolerance.
- On equal costs the current block keeps going, which favors fewer starts.
Follow-up questions
How do you handle the departure time being moved earlier?
Rerun from the current hour with the new deadline and the hours still needed. It takes microseconds, so recompute on any change rather than patching the old plan. If the need no longer fits, charge every remaining hour and tell the driver the shortfall.
How do you balance cost savings against minimizing charge cycles?
That is what start_cost does: it prices a cycle in the same units as electricity. Tune it, or expose it as a setting such as "prefer fewer interruptions".
How does preconditioning fit into the schedule?
Battery temperature changes how much charge an hour delivers. Add warm-up hours with their own cost before a block, or extend the state with a temperature band, which multiplies the table by the number of bands.
What if prices are revised after you have committed?
Replan from now, subtracting the hours already charged. Committing one hour at a time rather than the whole night keeps replanning cheap.
Common mistakes
- Taking the cheapest hours and ignoring the start cost.
- A state of only "hours charged so far", which cannot tell whether charging now starts a new block.
- Charging the start cost on every charging hour instead of only the first hour of a block.
- Returning only the cost when the question asks for the schedule.
Practice it in a mock interview
Reading a solution and explaining one under questioning are different skills. The button below starts this problem as a voice mock interview with Josh, Devana's AI interviewer, with an editor that runs your code.
Practice this problem in a mock interview
Related problems
- Compute a Diff Between Two File Versions: A longest common subsequence table over lines, then a walk that turns it into keep, delete and insert.
- Top K Documents Under a Latency Budget: Keep a min-heap of the best k seen so far; anything below its top is discarded at once.
- 2-D dynamic programming: the pattern guide, with more practice questions.