818. Race Car

1 题目理解

先讲规则。一辆小汽车停在位置0,并且方向朝向右侧,并且速度为1。小汽车每次可以选择加速A,那加速一次,新的位置=原来位置+速度,并且新的速度=速度*2。小汽车也可以选择掉头。掉头一次的话,新的位置=原来的位置,并且新的速度=如果原来速度>0,那就是-1,否则的话是1。
输入:int target。
输出:小汽车到达位置target最少需要几次操作。
例子:
Example 1:
Input:
target = 3
Output: 2
Explanation:
最短的操作顺序 是: “AA”.
汽车位置变化为: 0->1->3.

Example 2:
Input:
target = 6
Output: 5
Explanation:
最短的操作顺序 是 “AAARA”.
汽车位置变化为 0->1->3->7->7->6.

题目分析思路来源于花花酱

2 BFS

因为在每个位置上,有两种操作。在每次操作后速度、位置会发生变化。一直变,直到走到终点。所以可以按照BFS求解。遇到的第一个位置为target,就是最短的操作。
这里有几个关键点。
关键点1:如果相同的位置和速度已经加入过队列,那就不需要再次计算。
关键点2:如果在位置0,小汽车朝向右侧一定比朝向左侧用的操作最少。这个可以用反证法证明。
关键点3:小汽车向右走,可以超过target,但 不能超过2*target。否则操作数会更多。
当然这个方法是不能AC的。

class Solution {
    public int racecar(int target) {
        Queue<int[]> queue = new LinkedList<int[]>();
        List<String> filter = new ArrayList<String>();
        queue.offer(new int[]{0,1});//pos speed
        filter.add(0+"_"+1);
        filter.add("0_-1");
        int step = 0;
        
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i=0;i<size;i++){
                int[] values = queue.poll();
                int pos = values[0];
                int speed = values[1];
                //A
                int newpos = pos + speed;
                if(newpos == target){
                    return step + 1;
                }
                if(pos < 2*target){
                    queue.offer(new int[]{newpos,speed*2});
                    filter.add(newpos+"_"+(speed*2));
                }
                //R
                if(speed >=0 ){
                    speed = -1;
                }else{
                    speed = 1;
                }
                String key = pos+"_"+speed;
                if(!filter.contains(key)){
                    queue.offer(new int[]{pos,speed});
                    filter.add(key);
                }
                
            }
            step++;
        }
        
        return -1;
    }
}

通过这个方法我们知道在同一位置可能有两种状态:速度向右、速度向左。这提示我们在做动态规划的时候可能需要维护二维数组。

3 dp

如果target=7,我们发现

操作位置速度
起始01
第1次A12
第2次A34
第3次A78

3.1 基本情况

如果 t a r g e t = 2 n − 1 target = 2^n-1 target=2n1,那么到达target的最少操作数是n。是dp中的基本情况。

在这里插入图片描述

3.2 递归方程分析

如果不是,则可能要考虑两种情况。第一种情况是先走过5达到7,因为7的最少操作数可以直接求解,是基本情况。然后再调头,需要走2个位置达到5。如果我们提前计算好了走距离2的最少操作数,那就可以求解了。第二种情况是可能走到j=1,2,3,4,然后再走剩下的距离。操作数之和就是达到5的操作数。两种情况求最小值就是答案。下面分别讨论。

令dp[i][0]表示从0达到位置i并且速度朝右的最小操作数。dp[i][1]表示从0达到位置i并且速度朝左的最小操作数。

3.2.1 先超过target再调头

例如上面到达7,再返回达到5的时候可能速度方向朝左,也可能朝右。
dp[7][0]=3,dp[7][1]=4。
达到7调头,这时候操作数是4。
继续走2个距离,这时候的操作数是dp[2][0],这个时候的方向是朝左的。也就是说dp[5][1]= 4 + dp[2][0]。为什么是dp[2][0]而不是dp[2][1]呢?dp[2][0]表示从0到达2,并且方向向右,又因为起始位置速度的方向也向右,所以可以认为走过2个位置,并且方向和起始方向相同的最少操作数是dp[2][0]。当到达7以后调头,方向向左了,走过2个距离并且速度方向一致,那操作数就是dp[2][0]。
当然dp[5][1],也可能是通过dp[2][1]获得,这个时候需要再加一个转向操作,所以dp[5][1]=4+dp[2][1]+1。两者取最小值。dp[5][1] = 4 + min(dp[2][0],dp[2][1]+1)。
同理,可以推出dpp[5][0] = 4 + min(dp[2][1],dp[2][0]+1)。

推出一般情况就是:
l = 2 n − 1 − t a r g e t l=2^n-1-target l=2n1target
d p [ t a r g e t ] [ 0 ] = n + 1 + m i n ( d p [ l ] [ 1 ] , d p [ l ] [ 0 ] + 1 ) dp[target][0] = n +1 + min(dp[l][1],dp[l][0]+1) dp[target][0]=n+1+min(dp[l][1],dp[l][0]+1)
d p [ t a r g e t ] [ 1 ] = n + 1 + m i n ( d p [ l ] [ 0 ] , d p [ l ] [ 1 ] + 1 ) dp[target][1] = n +1 + min(dp[l][0],dp[l][1]+1) dp[target][1]=n+1+min(dp[l][0],dp[l][1]+1)

3.2.2 不超过target

再参考上图target=5,如果不超过5。
那我们可以先走距离1,到达1以后再走距离4。到达5。
我们也可以先走距离2,到达2以后再走距离3。到达5。

我们也可以先走距离4,到达4以后再走距离1。到达5。

我们先分析先走距离1,到达1以后再走距离4。到达5的情况。达到1,如果方向向右,则需要先 调头,再调头,然后走距离4。那么dp[5][0] = dp[1][0]+2 + dp[4][0]。达到1,如果方向向左,则需要先调头,然后走距离4。那么dp[5][0] = dp[1][1]+1 + dp[4][0]。最终dp[5][0] = min(dp[1][1]+1,dp[1][0]+2) + dp[4][0]。
同理:dp[5][1] = min(dp[1][1]+1,dp[1][0]+2) + dp[4][1]。

最终代码

class Solution {
    public int racecar(int target) {
        int[][] dp = new int[target+1][2];
        dp[0][0] = 0;
        dp[0][1] = 1;
        for(int i=1;i<=target;i++){
            int n = (int)Math.ceil(Math.log(i+1)/Math.log(2));
            if((1<<n)  == i+1){
                dp[i][0] = n;
                dp[i][1] = n+1;
            }else{
                int left = ((1<<n)-1 - i);
                dp[i][0] = n+1+Math.min(dp[left][1],dp[left][0]+1);
                dp[i][1] = n+1+Math.min(dp[left][0],dp[left][1]+1);
                for(int j = 1;j<i;j++){
                    int min = Math.min(dp[j][0]+2,dp[j][1]+1);
                    dp[i][0]  =  Math.min(dp[i][0], min+dp[i-j][0]);
                    
                    dp[i][1]  =  Math.min(dp[i][1], min+dp[i-j][1]);
                }
            }
        }
        return Math.min(dp[target][0],dp[target][1]);
    }
}

4 说明

看到题目还是应该拿着具体数值分析一下。在某一特定情况下是怎么到达目的地的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值