我正在尝试解决以下练习(取自“算法设计手册”)
我们希望用两根手指计算在标准按键式电话上拨打给定n位数的最懒的方式。我们假设两个手指从*和#键开始,并且将手指从一个按钮移动到另一个按钮所需的努力与它们之间的欧几里德距离成比例。设计一种算法,计算拨号方法,包括移动手指最小的总距离,其中k是键盘上不同键的数量(标准电话k = 16)。尝试使用O(nk ^ 3)时间
对于初学者,请d=d1.d2....dn be the n-digit sequence
我出现的算法使用了两个二维(nxk)阵列L,R和
- L[a][b]:min cost to type the d[a]-digit with the left finger and having
the right finger at button b
- R[a][b]:same thing but with right and left instead.
为了填充这两个数组,我使用两个几乎相同的递归函数(因此我只发布一个L)
首先,
- L[a+1][b]=L[a][b]+cost_from_d[a]_to_d[a+1]
也就是说,我们将右手指放在按钮处,我们将左手指从d [a]移动到d [a + 1]
然后,如果b == d [a](即用右边拨打的最后一个数字),那么也存在其他“正确”的方式,以便通过保持右手指在左手指上键入d [a + 1] d [a]按钮原样,左手指从任何地方移动到d [a + 1]。
- L[a+1][b]=min(L[a+1][b],min(R[a][c]+cost_from_c_to_d[a+1]),)
为此,我首先找到最低成本,以便我们拨打d [a + 1],左边拨打d [a]右边。然后我将它与上部子弹的值进行比较并保持最小值。
填充数组后,只需找到最小L / R [n] [无论如何]
即可找到最低成本对我来说,这段代码似乎是正确的。然而,鉴于时间复杂度仅为O(nk)而不是O(nk ^ 3),我肯定会犯一些错误......
有没有人知道这些错误在哪里?
答案 0 :(得分:0)
您对部分求解函数(L和R)的定义看起来很合理。您的复发关系可能是也可能不正确 - 表达很难遵循。尝试整理它并扩展理由。你确定你已经考虑过所有的手指路径吗?可以用任何一只手键入上一个键。
品味的一个问题:我喜欢为每组的元素使用连续的字母组(比如i和j表示时间,a和b表示键)。使算法更易于阅读。
啊哈!你的递归关系错过了一些路径(一次移动两个手指)。
定义:(与你的相同)
让d[i]
成为要拨打的电话号码的第i个键。
定义L(i, x)
是键入第一个i键的最低成本,左手指在键d [i]处结束,右手指在键x处结束。
将R(i, y)
定义为最小成本...右手指以键d [i]结束,左手指结束于键y。
记忆关系:
L(i+1, x) = min of all
使用左手指键入上一个键的路径。右手指可能处于任何关键位置
L(i, w) + dist(d[i], d[i+1]) + dist(w, x)
# left distance # right distance
使用右手指键入上一个键的路径。左手指可能处于任何关键位置。
R(i, y) + dist(y, d[i+1]) + dist(d[i], x)
# left distance # right distance
(类似关系适用于函数R)
那么我们为什么要考虑一次移动两根手指?假设三角形不等式,除了打字之外,最佳表盘永远不会移动手指。但我们需要它来计算我们的部分解决方案。
在回顾中,部分解决方案功能的选择决定了算法。在上面,我们被迫考虑在最佳解决方案中从未结束的路径。
观察:假设三角形不等式,则存在最佳拨号,其中除了键入之外,手指从不移动。证明:给定一个手指移动但不打字的拨号,我们可以通过“懒惰”减少(或保持相同)成本,并在需要输入时移动它。
所以
为j<定义L(i, j)
i< = n是键入第一个i键的最佳成本,其中左手键入第i个键,右手最近用于键入第j个键。
j的复发<我
L(i+1, j) =
来自j<我知道左手输入了第i个键
L(i, j) + dist(d[i], d[i+1])
其他复发。
L(i+1, i) = min of all
从j = i我们知道右手键入了第i个键。 (左手以前用于键入第k个键)
R(i, k) + dist(d[k], d[i+1]) over all k < i
看起来像你的原始算法的嗡嗡声! :/发生了什么事!