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.
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.
Note:
1 <= target <= 10000
.
---------------------------------------------------------------
上来直接BFS,TLE,然后开始想应该怎么剪枝。。。剪的思路并不容易,看了官方中英文题解感觉都不是非常严格(中文题解的说明还有文案错误),这里详细写一下,首先要明白整个序列的可能操作:
- 整个序列不能以R结尾,因为R并不会移动距离,这点并不难想
- 连续的R操作可以看做是中间A了0次,实现了降速的效果,再多连续的R也没有任何意义,也就是RA{0}R
- 整个序列如果以R开头,都可以等效替代。例如RA{k1}RA{k2}...RA{kn}这个序列,可以用A{k2}...RA{kn}RA{k1}这个序列或者A{k2}...RA{kn}RRA{k1}这个序列来替代(虽然不知道整体序列是奇数还是偶数)
所以,整个序列可以表示成A{k1}RA{k2}...RA{kn}的形式,奇数位置是前进档,偶数位置是倒退档。A{k1},A{k3}...A{奇数位}可以互相交换,同理偶数位可以互联交换。因此,奇数位、偶数位都可以排个序,保证k1>=k3>=k5...,同理k2>=k4>=k6...。而且k1>k2,要不整个序列都前进不了。
接下来从距离的角度理解整个序列的操作,连续A k次,刚好距离是2^k-1。如果target对应A{k1}RA{k2}...RA{kn}的某个序列,那么target = [(2^{k1}-1)+(2^{k3}-1)+...]-[(2^{k2}-1)+(2^{k4}-1)+...],如果最小操作次数的情况下(2^{k1}-1)>target,也就是车已经开过了target,这时候[(2^{k2}-1)+(2^{k4}-1)+...]一定大于[(2^{k3}-1)+...],开过了要反向找补回来嘛。(2^{k1}-1)第一次大于target的时候,刚好k1是k+1,其中k是target表示成二进制后最高位1到最低位一共占了多少位,也就是k = math.floor(math.log2(t + 1))。所以如果这个时候如果车还不调头,也就是k1变成了k+2,[(2^{k2}-1)+(2^{k4}-1)+...]需要反向的次数就要更多,这和目前最小操作次数矛盾。因此,最小操作次数的情况下,k1<=k+1,也就是说开车范围要在[0,2^(k+1)]之间。
解法一:有了上面这个思路,就可以BFS不超时了。
import math
class Solution:
def racecar(self, target: int) -> int:
layers, dic = [[(0, 1)], []], {(0, 1)}
c, n, step = 0, 1, 0
bits = math.floor(math.log2(target+1))
upper = 1<<(bits+1)
while (layers[c]):
step += 1
for pos, speed in layers[c]:
rspeed = 1 if speed < 0 else -1
for cmd in ['A', 'R']:
npos, nspeed = (pos + speed, speed<<1) if cmd == 'A' else (pos, rspeed)
if (npos == target):
return step
if (npos<=upper and npos>=0 and (npos, nspeed) not in dic):
dic.add((npos, nspeed))
layers[n].append((npos, nspeed))
layers[c].clear()
c, n = n, c
解法二:解法二来自LeetCode官方解法,把每个位置看成节点,连续A k次,刚好距离是2^k-1看做是边,然后用dijstrala求最短路,但是对于这种相邻节点很多的场景,Dijstrala并没有比BFS快,同理也要限定范围,这里贴一下codes,这种解法还是逆向思维,其实并不推荐:
import heapq
class Solution(object):
def racecar(self, target):
K = target.bit_length() + 1
barrier = 1 << K
pq = [(0, target)]
dist = [float('inf')] * (2 * barrier + 1)
dist[target] = 0
print(K)
while pq:
steps, targ = heapq.heappop(pq) #targ表示从起点0开始,经过steps,距离target有多远
if dist[targ] > steps: continue
for k in range(K+1):
walk = (1 << k) - 1
steps2, targ2 = steps + k + 1, walk - targ
if walk == targ: steps2 -= 1 #No "R" command if already exact
if abs(targ2) <= barrier and steps2 < dist[targ2]:
heapq.heappush(pq, (steps2, targ2))
dist[targ2] = steps2
return dist[0]
解法三:解法三用DP,但是对最有子问题的挖掘就变得更加深入了。
回到刚才target对应A{k1}RA{k2}...RA{kn}的某个序列,k = math.floor(math.log2(t + 1)),k1<=k+1,这是冲过target的情况。同理,可以证明最优解时候的k1>=k,因为如果k1=k-1,那么正向的冲刺就要降速到0再来一遍,操作次数从k1变成2*k1。所以最有解的k1只有k和k+1两种情况,那么递推表达式来了,利用f(t)表示冲向t时的最小次数,t的范围满足2^k-1 <= t < 2^(k+1)-1
- 如果连续A操作k1=k次刚好到达t,那么最优解就有了,也就是k1=k 而且 t=2^k-1,那么f(t)=k
- 如果连续A操作k1=k+1次,也就是冲过了t,那么最优解可能是k+2+f(2^(k+1)-1-t)
- 如果连续A操作k1=k次,刚好没过t,k1的次数也不能再少了,此时需要调头,调头后冲刺连续操作A共i次,那么最优解可能是k+2+i+f(t-(2^k-1)+(2^i-1))
所以代码是:
import math
class Solution:
#A{k1}RA{k2}A{k3}...A{kn},结尾一定不带
def f(self, t, memo):
k = math.floor(math.log2(t + 1))
if ((1 << k) - 1 == t):
return k
elif (t in memo):
return memo[t]
res = k + 2 + self.f((1 << (k + 1)) - 1 - t, memo) #超过target的情况
#A{k}RA{i}R,剩下的是f(t-((1<<k)-1)+((1<<i)-1))
for i in range(k):
res = min(res, k+i+2+self.f(t-((1<<k)-1)+((1<<i)-1),memo))
memo[t] = res
return res
def racecar(self, target: int) -> int:
memo = {1: 1, 3: 2, 2: 4}
res = self.f(target, memo)
return res