携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
赛车
你的赛车可以从位置 0 开始,并且速度为 +1 ,在一条无限长的数轴上行驶。赛车也可以向负方向行驶。赛车可以按照由加速指令 'A' 和倒车指令 'R' 组成的指令序列自动行驶。 当收到指令 'A' 时,赛车这样行驶: position += speed speed *= 2 当收到指令 'R' 时,赛车这样行驶: 如果速度为正数,那么speed = -1 否则 speed = 1 当前所处位置不变。 给你一个目标位置 target ,返回能到达目标位置的最短指令序列的长度。
分析
- 要想得到最短指令序列的长度,最短序列中如果包括前进和后退,先前进再后退和先后退再前进的序列长度是相等的,那就以先前进的角度来思考问题
- 连续A指令的情况下,可以达到的位置为1,2,4,8...符合等比数列的特征,如果target是等比数列里的,比如target=2^k,那连续的A指令就是最短的序列长度,即长度为k,序列为A^k
- 如果target在两个相邻的等比数列项之间,有两种到达target的方式:
- 方式1:赛车连续向前加速后通过target,即到不小于target的位置,之后通过倒退到达target
- 方式2:赛车连续加速后到达不大于target的位置,通过倒退一些距离,然后再通过向前加速到达target
- 这两种方式可以得到状态转移方程,使用动态规划来解决这道题目
动态规划
- 定义状态:dp[i]表示赛车到位置i,最短的序列长度为n
- 状态转移说明:
- 位置正好是等比数列中的某个位置,dp[i] = dp[i]
- 方式1,如果位置到j,此时序列长度为m,因为有一次转向,所以dp[i]=Math.min(dp[i],m+1+dp[i-j])
- 方式2,如果位置到j,此时序列长度为m,需要后退到为k,倒退到k需要的序列长度为p,涉及到两次转向,因此dp[i]=Math.min(dp[i],m+1+k+1+dp[i - (j - k)])
- 等比通项是t=2^k,位运算比计算pow更快,因此选择位运算
代码
const racecar = (target) => {
let dp = Array(target + 1);
for (let i = 1; i <= target; i++) {
dp[i] = Number.MAX_SAFE_INTEGER;
let j = 1, cnt1 = 1;
for (; j < i; j = (1 << cnt1) - 1) {
for (let k = 0, cnt2 = 0; k < j; k = (1 << cnt2) - 1) {
dp[i] = Math.min(dp[i], cnt1 + 1 + cnt2 + 1 + dp[i - (j - k)]);
cnt2++;
}
cnt1++;
}
dp[i] = Math.min(dp[i], cnt1 + (i == j ? 0 : 1 + dp[j - i]));
}
return dp[target];
};
总结
- 当动态规划的题目融合了一点数学相关的知识,就变得更有趣了。当算法有了现实意义,现实问题就可以更好的解决,这可能就是不断优化算法、不断探索算法的意义吧
- 今天也是有收获的一天