如何解决“编程挑战(编程竞赛培训手册)”中提出的“密码踢球者”练习?

时间:2010-02-01 08:05:11

标签: algorithm backtracking

“编程挑战(编程竞赛培训手册)”可能是关于算法的最好的练习册之一。 我已经解决了前11个练习,但现在我遇到了“Crypt Kicker”问题:

  

地穴踢球者
  加密文本的一种常见但不安全的方法是置换字母表中的字母。   换句话说,字母表中的每个字母在文本中始终被其他字母替换。为了确保加密是可逆的,没有两个字母被相同的字母替换。

     

您的任务是解密几个编码的文本行,假设每行使用不同的替换集,并且解密文本中的所有单词都来自已知单词的字典。

     

输入
  输入由一个包含整数n的行组成,后跟n个小写单词,每行一个,按字母顺序排列。这n个单词组成可能出现在解密文本中的单词词典   字典后面有几行输入。每条线都按上述方式加密。

     

字典中的字数不超过1,000个。没有字超过16   字母。加密行仅包含小写字母和空格   长度不超过80个字符。

     

输出
  解密每一行并将其打印到标准输出。如果有多种解决方案,任何人都会这样做   如果没有解决方案,请用星号替换字母表中的每个字母。

     

示例输入 6
    和
    迪克
    简
    粉扑
    现货
    yertle

     

bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn
    xxxx yyy zzzz www yyyy aaa bbbb ccc dddddd

     

示例输出
   迪克和简和粉扑,斑点和yertle ...

我应该采取什么策略才能解决此问题?我正在考虑一个经典而野蛮的回溯解决方案,但我正在努力避免这种情况,直到我找到更聪明的东西。

PS:这不是与家庭作业有关,我只是想提高我的整体技能。

5 个答案:

答案 0 :(得分:9)

KeyArray将保留替换表。

  • 从空的KeyArray开始,这是版本0

  • 将最长的加密字与最长字典字匹配,并添加到KeyArray (如果有两个最长,选择任何),这是版本1.

  • 解密下一个最长的加密词的一些字母。

  • 检查解密的字母是否与相同的字母匹配 在相同长度的任何字典单词中的位置。
  • 如果没有匹配,请返回版本0并尝试另一个字。
  • 如果某些字母匹配,请将其余字母添加到KeyArray,这是版本2.

  • 解密下一个最长的加密词的一些字母。

  • 检查解密的字母是否与相同的字母匹配 在任何词典单词中的位置。
  • 如果没有匹配,请返回版本1并尝试使用其他字词
  • 如果某些字母匹配,请将其余字母添加到KeyArray,这是版本3.

重复直到所有单词都被解密。

如果版本0中没有一个最长的单词创建部分解密 较短的单词,很可能没有解决方案。

答案 1 :(得分:3)

可以通过在回溯运行之前枚举可能性来进行次要优化。在Python中:

dictionary = ['and', 'dick', 'jane', 'puff', 'spot', 'yertle']
line = ['bjvg', 'xsb', 'hxsn', 'xsb', 'qymm', 'xsb', 'rqat', 'xsb', 'pnetfn']

# ------------------------------------

import collections

words_of_length = collections.defaultdict(list)

for word in dictionary:
  words_of_length[len(word)].append(word)

possibilities = collections.defaultdict(set)
certainities = {}

for word in line:
    length = len(word)
    for i, letter in enumerate(word):
        if len(words_of_length[length]) == 1:
            match = words_of_length[length][0]
            certainities[letter] = match[i]
        else:
            for match in words_of_length[length]:
              possibilities[letter].add(match[i])

for letter in certainities.itervalues():
    for k in possibilities:
        possibilities[k].discard(letter)

for i, j in certainities.iteritems():
    possibilities[i] = set([j])

# ------------------------------------

import pprint
pprint.pprint(dict(possibilities))

输出:

{'a': set(['c', 'f', 'o']),
 'b': set(['d']),
 'e': set(['r']),
 'f': set(['l']),
 'g': set(['f', 'k']),
 'h': set(['j', 'p', 's']),
 'j': set(['i', 'p', 'u']),
 'm': set(['c', 'f', 'k', 'o']),
 'n': set(['e']),
 'p': set(['y']),
 'q': set(['i', 'j', 'p', 's', 'u']),
 'r': set(['j', 'p', 's']),
 's': set(['n']),
 't': set(['t']),
 'v': set(['c', 'f', 'o']),
 'x': set(['a']),
 'y': set(['i', 'p', 'u'])}

如果你有一些单元素的可能性,你可以从输入中消除它们并重新运行算法。

编辑:切换设置而不是列表并添加打印代码。

答案 2 :(得分:2)

我实际上尝试了一种截然不同的方法。我从词典中建了一个特里词。然后我递归地遍历trie和句子(遍历DFS中的trie)。

在每个空格中,我确保在trie中找到一个单词的结尾,如果是,我将循环回根。在此过程中,我一直跟踪到目前为止我所做的字母分配。如果我的作业与之前的作业相矛盾,那么我就会失败并将递归解开,我可以做出下一个可能的分配。

听起来很棘手,但似乎效果很好。编码真的不是那么难!

答案 3 :(得分:0)

另一种可能的优化,如果您有足够的文本来处理并且您知道文本的语言,则可以使用字母频率(请参阅:http://en.wikipedia.org/wiki/Letter_frequency)。当处理6/7字时,这当然是一种非常近似的方法,但如果您有几页要解码,这将是最快的方法。

编辑:关于Max的解决方案,您也可以尝试提取单词的某些特征,例如重复字母。显然,在字典中加注,加密文本中的qymm是唯一以双字母结尾的四个字母单词,给出了3个字母的直接答案。在更复杂的场景中,您应该能够缩小每对信件的可能性。

答案 4 :(得分:-1)

这是Java实现,对@CarlosGutiérrez提出的algorithm进行了进一步完善。

Crypt Kicker Algorithm and Solution, what went wrong?

  • 改进之处在于添加单词模式以减少单词的搜索空间。例如,单词“ abc”和“她”具有相同的模式,而“ aac”和“她”不是三个不同字母的单词,不会与两个字母不同的单词匹配。

  • 此外,该算法可以递归实现,更加直观和明智。