Algorithm 以最少的移动次数打开锁

Algorithm 以最少的移动次数打开锁,algorithm,dynamic-programming,Algorithm,Dynamic Programming,考虑一个由车轮系统组成的锁。每个轮子按顺序有26个字母,每个轮子都用'a'初始化。如果向上移动一个轮子,该轮子的显示将移动到字母表的下一个字母;另一方面,向下移动滚轮,将显示切换到字母表的前一个字母。例如: ['a'] -> UP -> ['b'] ['b'] -> DOWN -> ['a'] ... ['z'] -> UP -> ['a'] ['a'] -> DOWN -> ['z'] 只需轻轻一挥,就可以在同一方向上移动任何连续的轮子

考虑一个由车轮系统组成的锁。每个轮子按顺序有26个字母,每个轮子都用
'a'
初始化。如果向上移动一个轮子,该轮子的显示将移动到字母表的下一个字母;另一方面,向下移动滚轮,将显示切换到字母表的前一个字母。例如:

['a'] ->  UP  -> ['b']
['b'] -> DOWN -> ['a']
...
['z'] ->  UP  -> ['a']
['a'] -> DOWN -> ['z']
只需轻轻一挥,就可以在同一方向上移动任何连续的轮子子序列。这与以这种方式移动子序列的所有轮子的效果相同,只需一次运动。例如,如果目标字符串是
'zzzzzzz'
,则将
'a'
更改为
'z'
的单个移动将使整个车轮序列从
'a'
更改为
'z'
,从而到达目标字符串——打开锁

如何确定打开锁的最少移动次数?这个问题有动态解决方案吗?该算法必须产生以下结果:

           Target string         | # moves
   ______________________________ __________
1 | abcxyz                       |    5
2 | abcdefghijklmnopqrstuvwxyz   |   25
3 | aaaaaaaaa                    |    0
4 | zzzzzzzzz                    |    1
5 | zzzzbzzzz                    |    3
案例1,目标
abcxyz

aaa[aaa] -> DOWN -> aaazzz
aaa[zz]z -> DOWN -> aaayyz
aaa[y]yz -> DOWN -> aaaxyz
a[aa]xyz ->  UP  -> abbxyz
ab[b]xyz ->  UP  -> abcxyz
# = 0 | abcxyz | _DDUUU  (choose the smallest subsequence to normalize)
# = 1 | aabxyz | __DUUU  (choose the smallest subsequence to normalize)
# = 2 | aaaxyz | ___UUU  (choose the smallest subsequence to normalize)
# = 3 | aaayza | ___UU_  (choose the smallest subsequence to normalize)
# = 4 | aaazaa | ___U__  (choose the smallest subsequence to normalize)
# = 5 | aaaaaa | ______  (choose the smallest subsequence to normalize)
案例5,目标
zzbzzzz

[aaaaaaaaa] -> DOWN -> zzzzzzzzz
zzzz[z]zzzz ->  UP  -> zzzzazzzz
zzzz[a]zzzz ->  UP  -> zzzzbzzzz

这个问题可以重新表述为:

将字符串S转换为只包含“a”的字符串的最小移动次数是多少

定义

将连续子序列视为字符串中相等字符的序列。最小的连续子序列自然是单个字符。如果规范化小的子序列,自然会得到更大的子序列,最终会得到一个子序列——整个字符串

标准化为什么内容

一个人只能上下移动一个角色,因此,角色本身就是一系列上下移动。字符表示的最坏的情况是字母表中的字母,这至少需要<代码> LEN(字母表)/2 < /CUD>移动来描述。在字母表
{a..z}
中,最坏的情况是
'm'
'n'

因为我们想尽量减少移动的次数,所以我们需要下拉字母
C=n
。因此,为了最小化规范化过程,我们必须找到需要相等规范化移动的最大子序列。例如,如果我们有一个目标
zzbzzzz
,我们知道最小方向是
uuduuu
——U表示向上,D表示向下

规范化

对于每次移动,计数器都会递增,从而使转换字符串所需的移动次数最少。考虑到上述示例,我们可以采取以下步骤:

# = 0 | zzzzbzzzz | UUUUDUUUU  (choose the smallest subsequence to normalize)
# = 1 | zzzzazzzz | UUUUDUUUU  (since 'a' is the base character, we choose
                              the move that increases the largest subsequence;
                              if 'a' was the first or last character,
                              moving it would simply be overhead)
# = 2 | zzzzzzzzz | UUUUUUUUU  (choose the subsequence to normalize)
# = 3 | aaaaaaaaa | _________  (choose the subsequence to normalize)
另一个示例,使用目标字符串
abcxyz

aaa[aaa] -> DOWN -> aaazzz
aaa[zz]z -> DOWN -> aaayyz
aaa[y]yz -> DOWN -> aaaxyz
a[aa]xyz ->  UP  -> abbxyz
ab[b]xyz ->  UP  -> abcxyz
# = 0 | abcxyz | _DDUUU  (choose the smallest subsequence to normalize)
# = 1 | aabxyz | __DUUU  (choose the smallest subsequence to normalize)
# = 2 | aaaxyz | ___UUU  (choose the smallest subsequence to normalize)
# = 3 | aaayza | ___UU_  (choose the smallest subsequence to normalize)
# = 4 | aaazaa | ___U__  (choose the smallest subsequence to normalize)
# = 5 | aaaaaa | ______  (choose the smallest subsequence to normalize)
编辑

正如@user1884905所指出的,这个解决方案,正如它所建议的,不是最优的。对于目标字符串
mn
,该算法不会产生最佳解决方案:

# = 0  | mn | DU  (choose the smallest subsequence to normalize)
# = 1  | ln | DU  (choose the smallest subsequence to normalize)
# = 2  | kn | DU  (choose the smallest subsequence to normalize)
...
# = 12 | an | _U  (choose the smallest subsequence to normalize)
# = 13 | ao | _U  (choose the smallest subsequence to normalize)
# = 14 | ap | _U  (choose the smallest subsequence to normalize)
...
# = 24 | aa | __  (choose the smallest subsequence to normalize)
这不是最优的,因为以下步骤需要较少的移动:

#0    #1    #2    ...    #12
mn -> mm -> ll -> ... -> aa

贪婪算法的最佳子结构可能在于减少字符与字符串之间的全局距离,而不是关注这些字符与基本大小写(
'a'
)之间的差异。

因为这只是一些附加信息,也可能是一些优化,这应该是对鲁本斯答案的评论,但在回答中,我可以更好地解释它,它也可以对提问者有用

我还使用了鲁本斯伟大的反向思想

因此,我认为没有必要将
a
旋转到其他位置。如果这是正确的(我没有反例),我们就不会把某个东西旋转到错误的方向(我没有数学家的证明,但可能这是正确的)

因此,
U
s和
D
s的每个子序列将以一个运动每次旋转。此算法不会花费O(n^2)时间。以下是算法:

我们称鲁本斯的字符串为方向字符串

  • 将计数器设置为0
  • 计算方向字符串
  • 扫描方向字符串
  • 如果您发现U或D字母的连续子序列,请在朝向
    a
    的位置旋转目标字符串,并增加计数器(每个子序列一次)
  • 如果有任何操作,请返回步骤2
  • 此算法将每个轮子旋转到
    a
    ,最多扫描
    k/2
    后即可完成,其中
    k
    是字母表元素的计数,因此这可能是一个线性时间运行的解决方案


    也许有一个解决方案,甚至更少的操作。这只是一个想法,通过寻找递增、递减或“山形”子序列并提取最大值。例如:我们可以说,不需要计算,求解的成本

    • abcde
    • 欧洲央行
    • abceeddcb
    等于解决单个
    e

    编辑:我看到了user1884905的反例。所以我的算法不会找到最优解,但是它可以用来找到正确的算法,所以我还没有删除它


    编辑2:另一个适用于示例目标字符串的想法:可以计算出一个平均字母。它是距离目标字符串字母的距离总和最小的字符串。每个字母都应该用上面的算法在这里旋转,然后整个字符串可以旋转到
    aaaaaaaa
    。由于字母表是循环的,因此可能有多个平均字母(如问题中的第二个示例),在这种情况下,我们应该选择距离
    a
    最小的字母

    不清楚你想要什么?你在找算法吗?为什么是三步?你不能做两个动作吗?@Jan