Minimum Time to Finish the Race
HardPrompt
You are given a 0-indexed 2D integer array tires where tires[i] = [fi, ri] indicates that the ith tire can finish its xth successive lap in fi * ri(x-1) seconds.
- For example, if
fi = 3andri = 2, then the tire would finish its1stlap in3seconds, its2ndlap in3 * 2 = 6seconds, its3rdlap in3 * 22 = 12seconds, etc.
You are also given an integer changeTime and an integer numLaps.
The race consists of numLaps laps and you may start the race with any tire. You have an unlimited supply of each tire and after every lap, you may change to any given tire (including the current tire type) if you wait changeTime seconds.
Return the minimum time to finish the race.
Example 1:
Input: tires = [[2,3],[3,4]], changeTime = 5, numLaps = 4
Output: 21
Explanation:
Lap 1: Start with tire 0 and finish the lap in 2 seconds.
Lap 2: Continue with tire 0 and finish the lap in 2 * 3 = 6 seconds.
Lap 3: Change tires to a new tire 0 for 5 seconds and then finish the lap in another 2 seconds.
Lap 4: Continue with tire 0 and finish the lap in 2 * 3 = 6 seconds.
Total time = 2 + 6 + 5 + 2 + 6 = 21 seconds.
The minimum time to complete the race is 21 seconds.Example 2:
Input: tires = [[1,10],[2,2],[3,4]], changeTime = 6, numLaps = 5
Output: 25
Explanation:
Lap 1: Start with tire 1 and finish the lap in 2 seconds.
Lap 2: Continue with tire 1 and finish the lap in 2 * 2 = 4 seconds.
Lap 3: Change tires to a new tire 1 for 6 seconds and then finish the lap in another 2 seconds.
Lap 4: Continue with tire 1 and finish the lap in 2 * 2 = 4 seconds.
Lap 5: Change tires to tire 0 for 6 seconds then finish the lap in another 1 second.
Total time = 2 + 4 + 6 + 2 + 4 + 6 + 1 = 25 seconds.
The minimum time to complete the race is 25 seconds.
Constraints:
1 <= tires.length <= 105tires[i].length == 21 <= fi, changeTime <= 1052 <= ri <= 1051 <= numLaps <= 1000
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a recursive function to explore all possible ways to finish the race. For a given number of laps n, it tries all possible lengths k for the first "stint" (a sequence of laps on a single tire). It calculates the time for this stint of k laps, adds the changeTime, and recursively calls the function for the remaining n-k laps. The base case is when there are no laps left. This method explores many redundant subproblems, leading to very high time complexity.
Algorithm
- Precompute an array
bestStintwherebestStint[k]stores the minimum time to completekconsecutive laps using any single tire. This is a necessary pre-step for any recursive or dynamic programming solution. - Define a recursive function, say
solve(lapsRemaining), that calculates the minimum time to finish the given number of laps. - Base Case: If
lapsRemainingis 0, the time taken is 0. Return 0. - Recursive Step: For
nlaps remaining, explore all possibilities for the next stint:- Iterate
kfrom 1 ton. - The cost for a stint of
klaps followed by a change isbestStint[k] + changeTime + solve(n - k). - The cost for completing all
nlaps in a single stint (no change) isbestStint[n]. - The function returns the minimum of all these possibilities.
- Iterate
- The initial call is
solve(numLaps), but the logic needs to be slightly adjusted to handle the fact that the first stint does not incur achangeTime. - A clearer recursive formulation is
solve(n) = min(bestStint[n], min_{1 <= k < n} (bestStint[k] + changeTime + solve(n-k))).
Walkthrough
The brute-force recursive approach directly translates the problem's choices into a recursive structure. We define a function solve(n) that computes the minimum time for n laps. To compute solve(n), we consider two main scenarios:
- Single Stint: We complete all
nlaps using a single tire without any changes. The time for this is the minimum time to runnconsecutive laps, which we can precompute asbestStint[n]. - Multiple Stints: We complete the first stint of
klaps (where1 <= k < n), then pay thechangeTime, and then recursively find the minimum time for the remainingn-klaps. We try this for all possible values ofk.
The final answer for solve(n) is the minimum over all these possibilities. This method is simple to understand but computationally expensive because solve(m) for a given m will be called many times with the same input, leading to an exponential number of total calls.
// Note: This is a conceptual example. A pure recursive solution// would be too slow. It's presented to illustrate the concept// before optimizing with memoization or DP.class Solution { long[] bestStint; int changeTime; // Precomputation of bestStint would be done here... private long solve(int laps) { if (laps == 0) { return 0; } // Option 1: Run all 'laps' in one stint long minTime = (laps < bestStint.length) ? bestStint[laps] : Long.MAX_VALUE; // Option 2: Run k laps, change, then solve for laps-k for (int k = 1; k < laps; k++) { if (k < bestStint.length && bestStint[k] != Long.MAX_VALUE) { long recursiveResult = solve(laps - k); if (recursiveResult != Long.MAX_VALUE) { long time = bestStint[k] + this.changeTime + recursiveResult; minTime = Math.min(minTime, time); } } } return minTime; }}Complexity
Time
O(T*C + 2^numLaps)
Space
O(numLaps)
Trade-offs
Pros
Simple to conceptualize and implement.
Directly models the decision-making process described in the problem.
Cons
Extremely inefficient due to exponential time complexity.
Will result in a 'Time Limit Exceeded' error on most platforms for the given constraints.
Calculates the same subproblems multiple times, leading to a lot of wasted computation.
This approach solves the problem efficiently by using dynamic programming. It avoids the redundant computations of the recursive approach by storing the results of subproblems. We define dp[i] as the minimum time required to complete i laps. We compute dp[i] for i = 1, 2, ..., numLaps.
To find dp[i], we consider all possible lengths of the final stint of laps. If the final stint has length k, it means we previously completed i-k laps in an optimal time dp[i-k], then paid a changeTime cost, and finally ran the last k laps. The cost for these last k laps is precomputed as bestStint[k]. By iterating through all possible split points, we find the minimum time for i laps.
Algorithm
- Precomputation:
- Create an array
bestStintof sizenumLaps + 1, initialized with a large value. This array will store the minimum time to completekconsecutive laps on a single tire. - Iterate through each tire
[f, r]in the inputtires. - For each tire, calculate the cumulative time for
k=1, 2, ...consecutive laps. UpdatebestStint[k]with the minimum time found. - We can optimize this by stopping the calculation for a tire once a single lap's time becomes excessively large (e.g., greater than
changeTime + f), as it would always be better to switch tires.
- Create an array
- Dynamic Programming:
- Create a DP array
dpof sizenumLaps + 1, initialized with a large value. Setdp[0] = 0. - Iterate
ifrom 1 tonumLapsto computedp[i]. - For each
i, consider two possibilities to achieve the minimum time: a. Complete allilaps in a single stint. The time isbestStint[i]. So, initializedp[i] = bestStint[i]. b. Complete the race in multiple stints. The last stint has some lengthk(1 <= k < i). This means we completedi-klaps, paidchangeTime, and then ran the lastklaps. The time isdp[i-k] + changeTime + bestStint[k]. We check this for all possible previous completion pointsj = i-k. - The DP transition is:
dp[i] = min(dp[i], dp[j] + changeTime + bestStint[i-j])for1 <= j < i. - The final answer is
dp[numLaps].
- Create a DP array
Walkthrough
The core of this method is to build up the solution for n laps from the solutions for fewer laps. Let dp[i] be the minimum time to finish i laps.
First, we precompute an array bestStint where bestStint[k] is the minimum time to run k consecutive laps using any single tire. This is done by iterating through all available tires and simulating their performance for an increasing number of laps. Since lap times grow exponentially (r >= 2), a single tire is only optimal for a relatively small number of consecutive laps before it's better to switch. We can prune the simulation for a tire once its next lap time exceeds changeTime + f (cost to switch to a fresh tire of the same type).
After precomputation, we build the dp table:
dp[0] = 0(0 laps take 0 time).- For
ifrom 1 tonumLaps,dp[i]is the minimum of:bestStint[i]: The time to run allilaps in one go, without changing tires.dp[j] + changeTime + bestStint[i-j]for all1 <= j < i: The time to finishjlaps optimally, pay thechangeTime, and then run the remainingi-jlaps in a single stint.
By filling the dp table this way, dp[numLaps] will hold the minimum time for the entire race.
import java.util.Arrays; class Solution { public int minimumFinishTime(int[][] tires, int changeTime, int numLaps) { // bestStint[k] = min time to run k consecutive laps with one tire long[] bestStint = new long[numLaps + 1]; Arrays.fill(bestStint, Long.MAX_VALUE); // Precompute bestStint array for (int[] tire : tires) { long f = tire[0]; long r = tire[1]; long currentLapTime = f; long totalStintTime = 0; for (int k = 1; k <= numLaps; k++) { totalStintTime += currentLapTime; // A very large total time is not useful and can overflow. // The max possible answer is roughly numLaps * (max_f + changeTime) ~ 2*10^8. if (totalStintTime > 2_000_000_000L) { break; } bestStint[k] = Math.min(bestStint[k], totalStintTime); currentLapTime *= r; // Optimization: if the next lap is more expensive than changing + one lap with this same tire type if (currentLapTime > changeTime + f) { break; } } } // dp[i] = min time to finish i laps long[] dp = new long[numLaps + 1]; Arrays.fill(dp, Long.MAX_VALUE); dp[0] = 0; for (int i = 1; i <= numLaps; i++) { // Option 1: Run all i laps in one stint if (i < bestStint.length) { dp[i] = bestStint[i]; } // Option 2: Run j laps, change, then run i-j laps in the last stint for (int j = 1; j < i; j++) { if (dp[j] != Long.MAX_VALUE && (i - j) < bestStint.length && bestStint[i - j] != Long.MAX_VALUE) { dp[i] = Math.min(dp[i], dp[j] + changeTime + bestStint[i - j]); } } } return (int) dp[numLaps]; }}Complexity
Time
O(T * C + numLaps^2)
Space
O(numLaps)
Trade-offs
Pros
Guarantees finding the optimal solution.
Efficient enough to pass the given constraints.
Systematically builds the solution, avoiding re-computation.
Cons
The O(numLaps^2) complexity might be too slow if the
numLapsconstraint were significantly larger.
Solutions
Solution
class Solution {public int minimumFinishTime(int[][] tires, int changeTime, int numLaps) { final int inf = 1 << 30; int[] cost = new int[18]; Arrays.fill(cost, inf); for (int[] e : tires) { int f = e[0], r = e[1]; int s = 0, t = f; for (int i = 1; t <= changeTime + f; ++i) { s += t; cost[i] = Math.min(cost[i], s); t *= r; } } int[] f = new int[numLaps + 1]; Arrays.fill(f, inf); f[0] = -changeTime; for (int i = 1; i <= numLaps; ++i) { for (int j = 1; j < Math.min(18, i + 1); ++j) { f[i] = Math.min(f[i], f[i - j] + cost[j]); } f[i] += changeTime; } return f[numLaps]; }}class Solution {public: int minimumFinishTime(vector<vector<int>> &tires, int changeTime, int numLaps) { int cost[18]; memset(cost, 0x3f, sizeof(cost)); for (auto &e : tires) { int f = e[0], r = e[1]; int s = 0; long long t = f; for (int i = 1; t <= changeTime + f; ++i) { s += t; cost[i] = min(cost[i], s); t *= r; } } int f[numLaps + 1]; memset(f, 0x3f, sizeof(f)); f[0] = -changeTime; for (int i = 1; i <= numLaps; ++i) { for (int j = 1; j < min(18, i + 1); ++j) { f[i] = min(f[i], f[i - j] + cost[j]); } f[i] += changeTime; } return f[numLaps]; }};class Solution: def minimumFinishTime(self, tires: List[List[int]], changeTime: int, numLaps: int) -> int: cost = [inf] * 18 for f, r in tires: i, s, t = 1, 0, f while t <= changeTime + f: s += t cost[i] = min(cost[i], s) t *= r i += 1 f = [inf] * (numLaps + 1) f[0] = - changeTime for i in range(1, numLaps + 1): for j in range(1, min(18, i + 1)): f[i] = min(f[i], f[i - j] + cost[j]) f[i] += changeTime return f[numLaps]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.