Minimum Cost For Tickets
Medium · 1-D Dynamic Programming · Explanation and code example
Dynamic Programming (Space Optimized) - IV
How it works
The previous approach uses 366 slots, but we only ever look back at most 30 days. By using modular arithmetic with a size-31 array, we can reduce space to constant while still accessing the necessary previous states. The index d % 31 ensures we have access to all values within the 30-day window.
Steps
- Create a DP array of size 31.
- For each calendar day, use modular indexing
d % 31. - For non-travel days, copy from
(d - 1) % 31. - For travel days, compute the minimum using modular indices for lookback.
- Return
dp[last_travel_day % 31].
Complexity
- Time complexity:
O(n) - Space complexity:
O(1)since the size of thedparray is31.
Things to watch for
Misunderstanding Pass Duration Coverage
A 7-day pass bought on day d covers days d through d+6 (inclusive), not d+1 through d+7. Similarly, a 30-day pass covers days d through d+29. When finding the next uncovered day, check if days[j] < days[i] + duration, not days[j] <= days[i] + duration. This off-by-one error leads to passes covering one fewer day than they should.
Not Considering All Three Pass Options at Each Decision Point
At each travel day, you must consider all three pass types: 1-day, 7-day, and 30-day. Some solutions greedily choose based on the number of upcoming travel days, but this can be suboptimal. For example, a 7-day pass might be cheaper than seven 1-day passes even if you only travel twice in that week. Always compute and compare the cost of all three options.
Incorrect Base Case or Boundary Handling
When using bottom-up DP, ensure dp[n] = 0 (no cost needed after all travel days). When looking back for pass coverage (e.g., dp[max(0, d-7)]), handle negative indices by clamping to 0. Failing to properly initialize boundaries or handle edge cases like single-day trips or consecutive travel days can produce incorrect results.
Python code example
from typing import List
class Solution:
def mincostTickets(self, days: List[int], costs: List[int]) -> int:
dp = [0] * 31
i = 0
for d in range(1, 366):
if i >= len(days):
break
dp[d % 31] = dp[(d - 1) % 31]
if d == days[i]:
dp[d % 31] += costs[0]
dp[d % 31] = min(dp[d % 31], costs[1] + dp[max(0, d - 7) % 31])
dp[d % 31] = min(dp[d % 31], costs[2] + dp[max(0, d - 30) % 31])
i += 1
return dp[days[-1] % 31]
C++ code example
class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
vector<int> dp(31, 0);
int i = 0;
for (int d = 1; d <= 365; d++) {
if (i >= days.size()) break;
dp[d % 31] = dp[(d - 1) % 31];
if (d == days[i]) {
dp[d % 31] += costs[0];
dp[d % 31] = min(dp[d % 31], costs[1] + dp[max(0, d - 7) % 31]);
dp[d % 31] = min(dp[d % 31], costs[2] + dp[max(0, d - 30) % 31]);
i++;
}
}
return dp[days.back() % 31];
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.