【LeetCode】818. Race Car

Problem Link

概要

硬核DP问题,本问题设计状态转移方程较为困难。

给定初始位置pos0,速度speed1的“汽车”,有两种指令A(Accelerate)R(Reverse)可供使用:

  • A指令可使汽车的位置变为pos + speed,同时speed *= 2
  • R指令可重置汽车的速度为1,且方向相反。(您可能会担忧速度为0的情况,但这种情况并不存在)

现给出一个目标位置target,求出到达此位置使用的最少的操作数目。

动态规划

状态定义如下:

F[i]表示到达位置i所需要的最少操作数目。

状态转移方程的定义涉及到几种情况的讨论,首先定义好边界条件:

F[0] = 0

其次,设计状态转移方程时要考虑到3种情况,为了说明清楚这些情况,您需要在脑中描绘一根数轴。初始时,汽车处于原点位置,考虑汽车永远不进行R操作,一直往前开的情况,很容易发现,进行nA操作到达的位置为$2^n - 1$,假定目的地为t,那么有

  1. 存在$n \in N$,使得$t = 2^n - 1$,那么此时的答案F[t] = n。一路往前开即使最优解,任何的倒车操作都是无用的。

  2. 汽车开过了位置t,并从t右侧开回到t

    这种情况下,要明白一个重要事实:

    汽车一定会在刚刚开过t时执行R操作反向,没有必要继续远离t往前开。

    由于从位置$2^n - 1$能够反向回到$2^{n - 1}$且所需的步数与从$0$到$2^{n - 1} - 1$所需步数一致(可以参考这个例子,0->1->3->7->15->15->14->12->8,从15到8经过3步,从0到7也经过3步),因此若继续往前开,反向后经过一定的步数后必定会回到上个位置,因此往前开是徒增花费的无意义动作。

    因此,这种情况下,可以得到下列状态转移方程:

    F[i] = n + 1 + F[n - i]

    其中,n = ceil(log2(i)),代表距离t最近的右侧的能够全程加速开到的位置,开到后,执行反向操作R耗费1点,之后以位置n为原点,看作是一个从0到位置n - i的子问题。

  3. 汽车未开过位置t,进行了反向操作。

    在这种情况下,汽车会在到达位置t之前至少进行两次R操作,调整其位置。那么在这种情况下,可以得到下列状态转移方程:

    F[i] = j + k + 2 + F[i - (2^j - 2^k)]

    其中,j表示第一段连续的A操作的个数,k表示第一次反向后倒退的A操作的个数,2表示两次反向操作,而后可以看作是从新位置到位置i的一个子问题,其中新位置等于前进距离与倒退距离的差$(2^j - 1) - (2^k - 1)$。这里要注意jk的取值范围,j的范围是0 <= j < n,因为当前情况下不得开过位置tk的取值范围是0 <= k < j,当k == 0时代表原地重置速度,当k == j时则相当于没开。

综上所述,可以得到状态及状态转移方程如下:

F[i]表示到达位置i所需要的最少操作数目

1
2
3
4
5
6
7
F[0] = 0
n = ceil(log2(i + 1))
if (i == 2^n - 1) {
F[i] = log2(i + 1)
} else {
F[i] = min{n + 1 + F[n - i], min(0<=j<n, 0<=k<j){j + k + 2 + F[i - (2^j - 2^k)]}} \\ 情况23中取最小,3须枚举一下j, k的值。
}

未证明 情况3中j可固定为n - 1

附录

My Solution