考虑一个由车轮系统组成的锁。每个轮子按顺序有26个字母,每个轮子用'a'
初始化。如果向上移动一个轮子,该轮子的显示将移动到字母表的下一个字母;另一方面,向下移动一个轮,将显示切换到字母表的前一个字母。例如:
['a'] -> UP -> ['b']
['b'] -> DOWN -> ['a']
...
['z'] -> UP -> ['a']
['a'] -> DOWN -> ['z']
只需轻弹就可以将任何轮子的连续子序列移动到同一方向。这与以单一运动方式移动子序列的所有轮子具有相同的效果。例如,如果目标字符串为'zzzzzzzzz'
,则将'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
案例5,目标zzzzbzzzz
:
[aaaaaaaaa] -> DOWN -> zzzzzzzzz
zzzz[z]zzzz -> UP -> zzzzazzzz
zzzz[a]zzzz -> UP -> zzzzbzzzz
答案 0 :(得分:4)
此问题可能会重申为:
将字符串S转换为仅包含'a'的字符串的最小移动次数是多少?
<强>定义强>:
将连续的子序列视为字符串中相等字符的序列。最小的连续子序列自然是单个字符。如果你规范化小的子序列,你自然会得到更大的子序列,最终达到一个子序列 - 整个字符串。
规范化的内容:
只能向上或向下移动一个字符,因此,一个字符本身就是UP和DOWN移动的序列。表示字符的最坏情况是字母表中间的字母,这需要至少len(alphabet) / 2
个移动进行描述。在字母{a..z}
中,最糟糕的情况是'm'
和'n'
。
由于我们希望最大限度地减少移动次数,因此我们需要拉下来字母C <= m
,拉起那些C >= n
。因此,为了最小化归一化过程,我们必须找到需要相等归一化移动的最大子序列。例如,如果我们有一个目标zzzzbzzzz
,我们知道最小方向为UUUUDUUUU
- U代表UP,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
:
# = 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'
)。
答案 1 :(得分:2)
由于这只是一些额外的信息,也许是一些优化,这应该是对鲁本斯答案的评论,但在答案中我可以更好地解释它,它也可以对提问者有用。
我也使用鲁本斯的反面思想。
所以,我认为没有必要将a
转换为其他内容。如果这是正确的(我没有反例),我们不应该将某些东西转向错误的方向(我没有数学证明,但可能这是正确的)。
因此,U
和D
s的每个子序列每次都会以一个动作旋转。该算法不会花费O(n ^ 2)时间。这是算法:
我们打电话给鲁本斯的字符串direction string
a
的位置并递增计数器(每个子序列一次)。此算法会将每个轮子旋转到a
,并且最多会在k/2
次扫描后完成,其中k
是字母表元素的数量,所以这可能是一个在线性时间内运行的解决方案。
也许有一个解决方案甚至更少的操作。这只是一个想法,找到增加,减少或“山形”子子序列并提取最大值。例如:我们可以说没有计算,解决的成本
abcde
,ecb
,abceeddcb
等于解决单个e
编辑:我见过user1884905的反例。所以我的算法找不到最优解,但它可以用来找到正确的算法,所以我还没有删除它。
编辑2:使用示例目标字符串的另一个想法:可以计算平均字母。它是与目标字符串的字母之间的距离之和最小的那个。每个字母都应该使用上面的算法旋转,然后整个字符串可以旋转到aaaaaaaaaa
。由于字母表是循环的,因此可能存在多个平均字母(如问题中的第二个示例),在这种情况下,我们应该选择与a
的距离最小的字母。