一起刷LeetCode——赛车(动态规划)

115 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

赛车

你的赛车可以从位置 0 开始,并且速度为 +1 ,在一条无限长的数轴上行驶。赛车也可以向负方向行驶。赛车可以按照由加速指令 'A' 和倒车指令 'R' 组成的指令序列自动行驶。 当收到指令 'A' 时,赛车这样行驶: position += speed speed *= 2 当收到指令 'R' 时,赛车这样行驶: 如果速度为正数,那么speed = -1 否则 speed = 1 当前所处位置不变。 给你一个目标位置 target ,返回能到达目标位置的最短指令序列的长度。

来源:leetcode.cn/problems/ra…

分析

  • 要想得到最短指令序列的长度,最短序列中如果包括前进和后退,先前进再后退和先后退再前进的序列长度是相等的,那就以先前进的角度来思考问题
  • 连续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];
};

总结

  • 当动态规划的题目融合了一点数学相关的知识,就变得更有趣了。当算法有了现实意义,现实问题就可以更好的解决,这可能就是不断优化算法、不断探索算法的意义吧
  • 今天也是有收获的一天