Race Car

文章讨论了一种寻找从1到Target的最短路径问题,主要涉及两种方法:BFS(广度优先搜索)和动态规划(DP)。BFS解决方案是从1出发,以2的幂次递增速度进行加速或反向操作,时间复杂度为O(Target*log(Target))。DP方法则涉及将目标转换为二进制并考虑不同加速策略,同样具有O(Target*log(Target))的时间复杂度,但效率更高。
摘要由CSDN通过智能技术生成

Question:

请添加图片描述

思路:

这个题难度真的大. 尤其是dp的答案真的难…
比较普遍的思路, 就是BFS. 因为找最短路径嘛. 所以从1开始BFS两种choices. 然后设置level 直到find 到target, 就是最短路径. 然后 time complexity 是 O(Target*log(Target)), 其实是很快的.
看一眼code:

public int racecar(int target) {
    Deque<int[]> queue = new LinkedList<>();
    queue.offerLast(new int[] {0, 1}); // starts from position 0 with speed 1
    
    Set<String> visited = new HashSet<>();
    visited.add(0 + " " + 1);
    
    for (int level = 0; !queue.isEmpty(); level++) {
        for(int k = queue.size(); k > 0; k--) {
            int[] cur = queue.pollFirst();  // cur[0] is position; cur[1] is speed
            
            if (cur[0] == target) {
                return level;
            }
            
            int[] nxt = new int[] {cur[0] + cur[1], cur[1] << 1};  // accelerate instruction
            String key = (nxt[0] + " " + nxt[1]);
            
            if (!visited.contains(key) && 0 < nxt[0] && nxt[0] < (target << 1)) {
                queue.offerLast(nxt);
                visited.add(key);
            }
            
            nxt = new int[] {cur[0], cur[1] > 0 ? -1 : 1};  // reverse instruction
            key = (nxt[0] + " " + nxt[1]);
            
            if (!visited.contains(key) && 0 < nxt[0] && nxt[0] < (target << 1)) {
                queue.offerLast(nxt);
                visited.add(key);
            }
        }
    }
    
    return -1;
}

这个是网上的答案, 不是我写的. 因为我一直在理解Time Complexity, 懒得自己写了.
解释一下为什么是O(Target*log(Target)),
本质上bfs的算法就是每种possibilites 都try 一遍嘛, 那么在1 to Target中每个number 都算是一个位置, 从任意一个位置都可以reverse 或者 加速, 然后到每个位置时候的速度也是不同的.

首先, 我们的速度是以2^N来递增的, 就是speed = 1, 然后A -> speed = 2 -> speed = 4 -> speed = 8 -> speed 16,…
所以对于每一个number from 1 to target. 如果是in worst case, [1,target] 里所有包括的数字都会visit, 而且worst case, 设numebr k 是[1,target] 里面的any一个number. worst case 下, 每一个k的位置, 都会reach到一个speed at most = Target, 然后总共可以有的speed 数量就是2Log(Target) base 2, 因为还可以有negative speed, 所以✖️2. 那么一共我们有target 个number from 1 to Target. 那就是O(Target)O(log(Target)) = O(Targetlog(Target))
Space: O(Target
log(Target))

DP arrpoach

接下来最难的dynamic programming. 这个真是离谱.
我先放几个帮助理解的截图.

请添加图片描述
上面这个是原作者的解释, 大概意思就是, 我们想要reach 我们的target, 有2种方法.
首先我们要理解, 因为我们加速是以2N 在加速. 所以
1.要么直接一次性加速到超过Target, 接着reverse 再慢慢回Target,
2. 要么一次性加速到Target前面一点儿, 然后reverse 一次减速, 再reverse 回到正方向, 接着加速到Target

请添加图片描述
那么, 我们怎么控制我们的加速加到哪儿呢, Target毕竟是数字嘛, 我们的加速还是2进制的.那就好办了.
把target换成二进制bits呗. 然后它的二进制bits全是1就是我们加速到的那个位置.
比如说 Target = 5
5 的 二进制是 101
那我们就加速到 111 = 7, 接着再reverse, 往回走. 然后这个就是23 - 1. 因为111其实是20 +21 + 22 = 7.
然后因为summation(2I) I from 1 to n-1 = 2n - 1嘛, 所以就是我们要算出Target的binary bits有几位, 然后再2n-1 就是我们加速的头儿. 接着reverse 1次, 然后往回走. 往回走这段儿, 就其实可以利用dp了, 因为往回走这段儿的距离是 (2n-1 - target), 然后想要从速度1开始走这么远的路程, 本质上就是从1 加速到 (2n-1 - target),就是我们dp table 算的内容,dp[(2n-1 - target)].
上述是第一种方法,

然后我们还要run第二种方法, 因为有可能先加速到前一个再往后走更近.
那就是先加速到2n-1-1, 然后reverse 转向+减速. 往回走M个operation, M < target的bits 数量. 然后再reverse 往前走. 其中, 往回走M个A的时候, 每走一个A, 我们都会转向接着往前走. 说白了就是减速之后,把所有往回走一步就扭头和往回走M步再扭头都试一遍. 这里我不太清楚为什么要这么写, discussion 有个人的解释是这么写的, 有可能以后我能看懂.:

请添加图片描述

然后, 我们这种方法的数量就是相当于, 一开始n-1次加速, 然后两次reverse, 和m个加速. = n-1+2+m = m+n+1
然后第二次转向之后, 就又和基础dp一个意思了, 首先第二次转向之后的位置大概是, 2n-1-1 - (2m-1) 这个位置, 然后它和Target的距离是Target - 2n-1-1 + (2m-1). 所以从这个位置到Target的最短路径其实就是dp[Target - 2n-1-1 + (2m-1)], 就这么recursion 循环了.

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

dp 的 Time Complexity 也是 O(Target * Log(Target)), 然后其实就是recursion 的感觉, Target就是大概会有Target次main Recursion, 然后每个recursion 还有个log(Target)次数的loop. 所以也是O(Target * Log(Target)), 但肯定比BFS的那个效率高很多.虽然Complexity 一样
O(Target) space

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值