原题链接:https://leetcode.com/problems/race-car/
1.题目介绍
Your car starts at position 0 and speed +1 on an infinite number line. (Your car can go into negative positions.)
Your car drives automatically according to a sequence of instructions A (accelerate) and R (reverse).
When you get an instruction “A”, your car does the following: position += speed, speed *= 2.
When you get an instruction “R”, your car does the following: if your speed is positive then speed = -1 , otherwise speed = 1. (Your position stays the same.)
For example, after commands “AAR”, your car goes to positions 0->1->3->3, and your speed goes to 1->2->4->-1.
Now for some target position, say the length of the shortest sequence of instructions to get there.
1 <= target <= 10000.
你的赛车起始停留在位置 0,速度为 +1,正行驶在一个无限长的数轴上。(车也可以向负数方向行驶。)
你的车会根据一系列由 A(加速)和 R(倒车)组成的指令进行自动驾驶 。
当车得到指令 “A” 时, 将会做出以下操作: position += speed, speed *= 2。
当车得到指令 “R” 时, 将会做出以下操作:如果当前速度是正数,则将车速调整为 speed = -1 ;否则将车速调整为 speed = 1。 (当前所处位置不变。)
例如,当得到一系列指令 “AAR” 后, 你的车将会走过位置 0->1->3->3,并且速度变化为 1->2->4->-1。
现在给定一个目标位置,请给出能够到达目标位置的最短指令列表的长度。
1 <= target <= 10000.
Example 1:
Input:
target = 3
Output: 2
Explanation:
The shortest instruction sequence is "AA".
Your position goes from 0->1->3.
Example 2:
Input:
target = 6
Output: 5
Explanation:
The shortest instruction sequence is "AAARA".
Your position goes from 0->1->3->7->7->6.
2. 解题思路-动态规划
对于求极值的题目,主要有一下方法:
- 广度优先搜索BFS
- 深度优先搜索DFS(带剪枝)
- 贪心算法
- 动态规划(带记忆数组的递归也可以算动态规划)
比如这个题就用动态规划可以解决。
注:本文采用的动态规划解法参考了 https://blog.csdn.net/magicbean2/article/details/80333734 的部分介绍。
2.1 构造dp数组
采用动态规划的思路,定义 dp [ target ] 表示行驶长度为 target 的距离所需要的最小指示个数。
2.2 寻找递推关系式
1 . 如果 target 恰好是 2n - 1,那么到达该位置只需要 n 个 A 就可以了。也就是说前面一路加速,直到到达target为止。
2 . 如果不满足上面的1,那么最优解就有2种可能:
(a)一路加速,冲过target之后再调头后退,逐渐反向接近target。
此时我们已经走了n+1步(n个 A 和1个 R ),并且和target的距离已经减少到了2n - 1 - target,于是问题转化成了: 求行驶长度为 2n - 1 - target 的距离所需要的最小指示个数。也就是 dp[ 2n - 1 - target ]
(b)在冲过target之前,先向后退一点,然后再向target接近。
在冲过target的前一步停下来,调头向后走m个"A" ,走完m个"A"后,再次调头,此时距离 targe 的距离是target - [2(n-1)-1]+(2m-1) 。然后再使用递归函数求去往距离为 target - [2(n-1)-1]+(2m-1) 位置的最小指令数。其中的m可以是任何满足2m - 1 < 2(n-1)-1 的数,不一定是哪个,所以要从m = 0 开始遍历。
(这种情况的典型例子就是 target = 5 的时候,最小的指令数为7,指令为"AAR-AR-AA".)
最优解就是 (a) 和(b)之间的最小值。
我们使用带记忆数组的递归来进行动态规划。如果dp数组中已经有了对应的值,就直接使用;如果没有,使用递归函数计算需要的值并存储在dp数组里。
实现代码
class Solution {
int [] dp;
public int racecar(int target) {
dp = new int [target+1];
return recursion(target);
}
public int recursion(int target) {
if(dp[target] != 0) {
return dp[target];
}
//如果位置恰好是2^n-1,那么到达该位置只需要n个A就可以了。
double dn = Math.log(target )/Math.log(2);
int n = (int) Math.floor(dn) +1;
if ( (1<<n)-1 == target) {
dp[target] = n;
}
//对于位置不是2^n-1 的情况,就需要比较两种可能性,取其中较小的一个。
else {
//第一种
//冲过target之后再调头后退,和target的距离已经减少到了2^n - 1 - target
int a = recursion( (1 << n) - 1 - target) + n + 1;
//第二种
//在冲过target的前一步停下来,调头向后走m个"A"
//走完m个"A"后,再次调头,此时距离targe的距离是target-[2^(n-1)-1]+(2^m-1)
//然后再使用递归函数求去往距离为 target-[2^(n-1)-1]+(2^m-1) 位置的最小指令数
int b = Integer.MAX_VALUE;
for (int m = 0; m < n - 1; m++) {
int dist = target-( (1<<(n-1)) - 1 ) + ( (1<<m) - 1 );
b = Math.min( b , recursion(dist) + (n-1) + 1 + m + 1);
//即:先走(n-1)个A,然后调头1个R,再走m个A,再调头1个R,之后继续向target接近。
}
//取两者最小的
dp[target] = Math.min(a, b);
}
return dp[target];
}
}