818. Race Car
Problem Description
You have a race car on an infinite number line starting at position 0 with initial speed +1. The car can move into negative positions and operates based on two commands:
Command 'A' (Accelerate):
- First, update position:
position += speed - Then, double the speed:
speed *= 2
Command 'R' (Reverse):
- If current speed is positive: set
speed = -1 - If current speed is negative: set
speed = 1 - Position remains unchanged
For example, with the command sequence "AAR":
- Start: position =
0, speed =1 - After 'A': position =
0 + 1 = 1, speed =1 * 2 = 2 - After 'A': position =
1 + 2 = 3, speed =2 * 2 = 4 - After 'R': position =
3(unchanged), speed =-1(reversed from positive)
Given a target position target, find the minimum number of commands needed to reach that exact position.
The solution uses dynamic programming where dp[i] represents the minimum number of commands to reach position i. The algorithm considers different strategies:
- If position
iequals2^k - 1for somek, we can reach it with exactlykaccelerations - Otherwise, we can either overshoot and reverse back, or approach from below with a combination of accelerations and reversals
How We Pick the Algorithm
Why Dynamic Programming?
This problem maps to Dynamic Programming through a short path in the full flowchart.
DP over distances exploits the 2^k-1 reachable positions and overshoot/undershoot recursion to minimize the instructions.
Open in FlowchartIntuition
The key observation is understanding the positions we can reach with consecutive accelerations. Starting from position 0 with speed 1:
- After 1 'A': position =
1, speed =2 - After 2 'A's: position =
3, speed =4 - After 3 'A's: position =
7, speed =8 - After k 'A's: position =
2^k - 1, speed =2^k
Notice that consecutive accelerations give us positions 1, 3, 7, 15, 31... which are all of the form 2^k - 1. These are the "perfect" positions that can be reached most efficiently with exactly k accelerations.
For any other target position, we have two main strategies:
Strategy 1: Overshoot and come back
If our target is between 2^(k-1) - 1 and 2^k - 1, we can:
- Accelerate
ktimes to reach2^k - 1(overshooting the target) - Reverse to change direction (1 command)
- Then solve the subproblem of reaching the remaining distance
(2^k - 1) - targetin the opposite direction
This gives us the formula: dp[target] = k + 1 + dp[2^k - 1 - target]
Strategy 2: Approach from below Alternatively, we can:
- Accelerate
k-1times to reach2^(k-1) - 1(stopping short of target) - Reverse to change direction (1 command)
- Accelerate
jtimes backward to position2^(k-1) - 1 - (2^j - 1) - Reverse again to go forward (1 command)
- Solve the remaining distance to target
This gives us: dp[target] = (k-1) + 1 + j + 1 + dp[target - (2^(k-1) - 2^j)]
The optimal solution is the minimum among all these possible strategies. The bit length operation k = target.bit_length() helps us quickly find the smallest k such that 2^k > target, which determines the range of strategies we need to consider.
Pattern Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming with a bottom-up approach. We create an array dp where dp[i] stores the minimum number of commands needed to reach position i.
Step 1: Initialize the DP array
dp = [0] * (target + 1)
We build solutions for all positions from 1 to target.
Step 2: Process each position
For each position i from 1 to target:
First, calculate k = i.bit_length(), which gives us the smallest integer such that 2^k > i. This means i falls in the range [2^(k-1), 2^k - 1].
Step 3: Handle perfect positions
if i == 2**k - 1: dp[i] = k continue
If i equals 2^k - 1, we can reach it with exactly k consecutive accelerations. This is optimal, so we set dp[i] = k and move to the next position.
Step 4: Strategy 1 - Overshoot and return
dp[i] = dp[2**k - 1 - i] + k + 1
We accelerate k times to reach position 2^k - 1, then reverse (adding 1 command), and solve the subproblem of traveling (2^k - 1) - i distance in the opposite direction. The total cost is k accelerations + 1 reverse + dp[2^k - 1 - i].
Step 5: Strategy 2 - Approach from below with backtracking
for j in range(k - 1):
dp[i] = min(dp[i], dp[i - (2**(k - 1) - 2**j)] + k - 1 + j + 2)
We try all possible ways to approach from below:
- Accelerate
k-1times to reach2^(k-1) - 1 - Reverse (1 command)
- Accelerate
jtimes backward, moving2^j - 1units back - Reverse again (1 command)
- Solve remaining distance:
i - (2^(k-1) - 1) + (2^j - 1) = i - (2^(k-1) - 2^j)
The formula simplifies to:
(k-1)forward accelerations1reversejbackward accelerations1reversedp[i - (2^(k-1) - 2^j)]for the remaining distance
Total: (k-1) + 1 + j + 1 + dp[i - (2^(k-1) - 2^j)] = k - 1 + j + 2 + dp[i - (2^(k-1) - 2^j)]
Step 6: Return the result
After computing all positions up to target, return dp[target] as the minimum number of commands needed.
Example Walkthrough
Let's find the minimum number of commands to reach position 5.
Step 1: Build DP table from position 1 to 5
Position 1:
k = 1.bit_length() = 1(since2^1 = 2 > 1)- Check if
1 == 2^1 - 1 = 1: Yes! This is a perfect position dp[1] = 1(one 'A' command: pos 0→1)
Position 2:
k = 2.bit_length() = 2(since2^2 = 4 > 2)- Check if
2 == 2^2 - 1 = 3: No - Strategy 1 (overshoot): Go to 3, reverse, go back 1
dp[2] = k + 1 + dp[3-2] = 2 + 1 + dp[1] = 2 + 1 + 1 = 4
- Strategy 2 (approach from below): Go to 1, reverse, go back 0 positions, reverse, go forward 1
- For
j = 0:dp[2] = (k-1) + 1 + j + 1 + dp[2-(1-0)] = 1 + 1 + 0 + 1 + dp[1] = 3 + 1 = 4
- For
dp[2] = min(4, 4) = 4(for example "ARRA": go to 1, reverse twice, then accelerate to 2)
Position 3:
k = 3.bit_length() = 2(since2^2 = 4 > 3)- Check if
3 == 2^2 - 1 = 3: Yes! This is a perfect position dp[3] = 2(two 'A' commands: pos 0→1→3)
Position 4:
k = 4.bit_length() = 3(since2^3 = 8 > 4)- Check if
4 == 2^3 - 1 = 7: No - Strategy 1 (overshoot to 7):
dp[4] = 3 + 1 + dp[7-4] = 3 + 1 + dp[3] = 3 + 1 + 2 = 6 - Strategy 2 (from position 3):
- For
j = 0: Go to 3, reverse, don't go back, reverse, go 1 forwarddp[4] = 2 + 1 + 0 + 1 + dp[4-3] = 2 + 1 + 0 + 1 + dp[1] = 4 + 1 = 5
- For
j = 1: Go to 3, reverse, go back 1, reverse, go 2 forwarddp[4] = 2 + 1 + 1 + 1 + dp[4-(3-1)] = 2 + 1 + 1 + 1 + dp[2] = 5 + 4 = 9
- For
dp[4] = min(6, 5, 9) = 5(Commands: "AARRA" - go to 3, reverse twice, go to 4)
Position 5:
k = 5.bit_length() = 3(since2^3 = 8 > 5)- Check if
5 == 2^3 - 1 = 7: No - Strategy 1 (overshoot to 7):
dp[5] = 3 + 1 + dp[7-5] = 3 + 1 + dp[2] = 3 + 1 + 4 = 8 - Strategy 2 (from position 3):
- For
j = 0:dp[5] = 2 + 1 + 0 + 1 + dp[5-3] = 4 + dp[2] = 4 + 4 = 8 - For
j = 1:dp[5] = 2 + 1 + 1 + 1 + dp[5-(3-1)] = 5 + dp[3] = 5 + 2 = 7
- For
dp[5] = min(8, 8, 7) = 7
Answer: 7 commands minimum to reach position 5
One optimal sequence: "AARARAA"
- Start: pos=0, speed=1
- A: pos=1, speed=2
- A: pos=3, speed=4
- R: pos=3, speed=-1
- A: pos=2, speed=-2
- R: pos=2, speed=1
- A: pos=3, speed=2
- A: pos=5, speed=4
Solution Implementation
1class Solution:
2 def racecar(self, target: int) -> int:
3 # dp[i] stores the minimum number of instructions to reach position i
4 dp = [0] * (target + 1)
5
6 # Calculate minimum instructions for each position from 1 to target
7 for position in range(1, target + 1):
8 # Calculate the number of bits needed to represent the position
9 # This represents the minimum accelerations needed if we go straight
10 bits_needed = position.bit_length()
11
12 # Check if position is exactly reachable by continuous acceleration
13 # (i.e., position is of the form 2^k - 1)
14 if position == 2**bits_needed - 1:
15 # Can reach this position with exactly k accelerations
16 dp[position] = bits_needed
17 continue
18
19 # Case 1: Overshoot the target and then reverse
20 # Go to (2^k - 1), then reverse and cover the remaining distance
21 overshoot_position = 2**bits_needed - 1
22 remaining_distance = overshoot_position - position
23 dp[position] = dp[remaining_distance] + bits_needed + 1
24
25 # Case 2: Undershoot, reverse, go back some distance, then reverse again
26 # Try different amounts of backward movement
27 for backward_steps in range(bits_needed - 1):
28 # Forward distance: 2^(k-1) - 1 (one bit less than full)
29 # Backward distance: 2^j - 1 (where j is backward_steps)
30 forward_distance = 2**(bits_needed - 1) - 1
31 backward_distance = 2**backward_steps - 1
32 net_distance = forward_distance - backward_distance
33
34 # Calculate remaining distance to target
35 remaining = position - net_distance
36
37 # Total instructions: (k-1) forward + 1 reverse + j backward + 1 reverse + remaining
38 instructions = (bits_needed - 1) + 1 + backward_steps + 1 + dp[remaining]
39 dp[position] = min(dp[position], instructions)
40
41 return dp[target]
421class Solution {
2 public int racecar(int target) {
3 // dp[i] represents minimum instructions needed to reach position i
4 int[] dp = new int[target + 1];
5
6 // Calculate minimum instructions for each position from 1 to target
7 for (int i = 1; i <= target; ++i) {
8 // Find the number of bits needed to represent i (position of highest bit + 1)
9 // This represents the number of 'A' instructions if we go straight past target
10 int bitsNeeded = 32 - Integer.numberOfLeadingZeros(i);
11
12 // Check if current position is exactly reachable with consecutive 'A' instructions
13 // Position is 2^k - 1 (e.g., 1, 3, 7, 15, 31...)
14 if (i == (1 << bitsNeeded) - 1) {
15 // Can reach exactly with k consecutive 'A' instructions
16 dp[i] = bitsNeeded;
17 continue;
18 }
19
20 // Option 1: Overshoot the target, then reverse and come back
21 // Go to position (2^k - 1), reverse, then go to target
22 // Total: k 'A' instructions + 1 'R' + instructions to go back
23 int overshootPosition = (1 << bitsNeeded) - 1;
24 int distanceBack = overshootPosition - i;
25 dp[i] = dp[distanceBack] + bitsNeeded + 1;
26
27 // Option 2: Undershoot, reverse, go back some distance, reverse again, then continue
28 // Go to position (2^(k-1) - 1), reverse, go back (2^j - 1), reverse, continue to target
29 int undershootPosition = (1 << (bitsNeeded - 1)) - 1;
30
31 for (int backSteps = 0; backSteps < bitsNeeded; ++backSteps) {
32 // Calculate remaining distance after undershooting and going back
33 int backDistance = (1 << backSteps) - 1;
34 int remainingDistance = i - undershootPosition + backDistance;
35
36 // Total instructions: (k-1) 'A' + 1 'R' + j 'A' + 1 'R' + instructions for remaining
37 int instructionCount = (bitsNeeded - 1) + 1 + backSteps + 1 + dp[remainingDistance];
38
39 // Keep the minimum instruction count
40 dp[i] = Math.min(dp[i], instructionCount);
41 }
42 }
43
44 return dp[target];
45 }
46}
471class Solution {
2public:
3 int racecar(int target) {
4 // dp[i] represents the minimum number of instructions to reach position i
5 vector<int> dp(target + 1);
6
7 for (int i = 1; i <= target; ++i) {
8 // Find the number of bits needed to represent i (position of highest bit + 1)
9 // This represents the number of accelerations needed to reach or pass position i
10 int numBits = 32 - __builtin_clz(i);
11
12 // Check if i is exactly reachable by consecutive accelerations (2^numBits - 1)
13 // For example: positions 1, 3, 7, 15, 31... can be reached with only A instructions
14 if (i == (1 << numBits) - 1) {
15 dp[i] = numBits;
16 continue;
17 }
18
19 // Option 1: Overshoot the target, then reverse and come back
20 // Go to position (2^numBits - 1), reverse, then reach the remaining distance
21 int overshootPosition = (1 << numBits) - 1;
22 dp[i] = dp[overshootPosition - i] + numBits + 1; // +1 for the reverse instruction
23
24 // Option 2: Undershoot the target, reverse briefly, reverse again, then continue
25 // Go to position (2^(numBits-1) - 1), reverse, go back a bit, reverse again, continue
26 for (int backSteps = 0; backSteps < numBits; ++backSteps) {
27 int undershootPosition = (1 << (numBits - 1)) - 1; // Position after (numBits-1) accelerations
28 int backDistance = (1 << backSteps) - 1; // Distance traveled backwards
29 int remainingDistance = i - undershootPosition + backDistance;
30
31 // Total instructions: (numBits-1) forward + 1 reverse + backSteps backward + 1 reverse + remaining
32 dp[i] = min(dp[i], dp[remainingDistance] + (numBits - 1) + backSteps + 2);
33 }
34 }
35
36 return dp[target];
37 }
38};
391function racecar(target: number): number {
2 // dp[i] represents the minimum number of instructions to reach position i
3 const dp: number[] = new Array(target + 1).fill(0);
4
5 for (let i = 1; i <= target; i++) {
6 // Find the number of bits needed to represent i (position of highest bit + 1)
7 // This represents the number of accelerations needed to reach or pass position i
8 const numBits = 32 - Math.clz32(i);
9
10 // Check if i is exactly reachable by consecutive accelerations (2^numBits - 1)
11 // For example: positions 1, 3, 7, 15, 31... can be reached with only A instructions
12 if (i === (1 << numBits) - 1) {
13 dp[i] = numBits;
14 continue;
15 }
16
17 // Option 1: Overshoot the target, then reverse and come back
18 // Go to position (2^numBits - 1), reverse, then reach the remaining distance
19 const overshootPosition = (1 << numBits) - 1;
20 dp[i] = dp[overshootPosition - i] + numBits + 1; // +1 for the reverse instruction
21
22 // Option 2: Undershoot the target, reverse briefly, reverse again, then continue
23 // Go to position (2^(numBits-1) - 1), reverse, go back a bit, reverse again, continue
24 for (let backSteps = 0; backSteps < numBits; backSteps++) {
25 const undershootPosition = (1 << (numBits - 1)) - 1; // Position after (numBits-1) accelerations
26 const backDistance = (1 << backSteps) - 1; // Distance traveled backwards
27 const remainingDistance = i - undershootPosition + backDistance;
28
29 // Total instructions: (numBits-1) forward + 1 reverse + backSteps backward + 1 reverse + remaining
30 dp[i] = Math.min(dp[i], dp[remainingDistance] + (numBits - 1) + backSteps + 2);
31 }
32 }
33
34 return dp[target];
35}
36Time and Space Complexity
Time Complexity: O(n * log n)
The algorithm uses dynamic programming to compute the minimum number of instructions to reach each position from 1 to target.
- The outer loop iterates through all positions from 1 to
target, which isO(n)wheren = target. - For each position
i:- Computing
bit_length()takesO(log i)time. - If
i = 2^k - 1, the computation isO(1). - Otherwise, there's an inner loop that runs from
j = 0tok - 2, wherek ≈ log i. This inner loop performsO(log i)iterations. - Each iteration of the inner loop performs constant time operations (array lookups and comparisons).
- Computing
- Therefore, for each position
i, the work done isO(log i). - The total time complexity is
∑(i=1 to n) O(log i) = O(n * log n).
Space Complexity: O(n)
The algorithm uses a single array dp of size target + 1 to store the minimum number of instructions for each position from 0 to target. No other data structures scale with the input size, so the space complexity is O(n) where n = target.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Considering Only the Overshoot Strategy
The Pitfall: It is tempting to assume that the fastest route always accelerates past the target and then comes back. Dropping the undershoot loop gives wrong answers.
# Wrong: only the overshoot case dp[position] = dp[2**bits_needed - 1 - position] + bits_needed + 1
For target = 4, overshooting costs AAA (to 7) + R + dp[3] = 2, which is 6 commands. The undershoot route AARRA (to 3, reverse twice, then 1 more step) needs only 5. The overshoot-only version also returns 8 instead of 6 for target = 8.
Solution:
Always try both strategies and keep the minimum, as the loop over backward_steps does.
2. Skipping the Zero-Backward-Steps Case
The Pitfall:
In the undershoot strategy, it can look pointless to reverse twice without moving (backward_steps = 0). Starting the loop at 1 removes that option:
# Wrong: misses the "reverse twice in place" option
for backward_steps in range(1, bits_needed - 1):
Two reversals in a row reset the speed to +1 without changing position, which is exactly how AARRA reaches 4. With the loop starting at 1, target = 4 returns 6 instead of 5, and target = 8 returns 8 instead of 6.
Solution:
Start the loop at backward_steps = 0: for backward_steps in range(bits_needed - 1).
3. Adding Unnecessary Bounds Checks or a Larger DP Array
The Pitfall:
It is common to worry that dp[remaining_distance] or dp[remaining] could index past target or below 0, and to add bounds checks or allocate dp of size 2 * target.
Why it is not needed:
Since bits_needed = position.bit_length(), the position satisfies 2^(k-1) <= position < 2^k - 1 in the non-perfect case. The overshoot remainder 2^k - 1 - position is therefore at most 2^(k-1) - 1, which is smaller than position. In the undershoot case, remaining = position - (2^(k-1) - 2^j) is at least 1 (because 2^(k-1) - 2^j < 2^(k-1) <= position) and smaller than position (because j < k-1). Both indices always fall in [0, position), so they are already computed when position is processed in increasing order.
Solution:
Keep dp of size target + 1 and fill it in increasing order of position. No extra bounds checks are required.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhat's the relationship between a tree and a graph?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!