“编程挑战(编程竞赛培训手册)”可能是关于算法的最好的练习册之一。 我已经解决了前11个练习,但现在我遇到了“Crypt Kicker”问题:
地穴踢球者
加密文本的一种常见但不安全的方法是置换字母表中的字母。 换句话说,字母表中的每个字母在文本中始终被其他字母替换。为了确保加密是可逆的,没有两个字母被相同的字母替换。您的任务是解密几个编码的文本行,假设每行使用不同的替换集,并且解密文本中的所有单词都来自已知单词的字典。
输入
输入由一个包含整数n的行组成,后跟n个小写单词,每行一个,按字母顺序排列。这n个单词组成可能出现在解密文本中的单词词典 字典后面有几行输入。每条线都按上述方式加密。字典中的字数不超过1,000个。没有字超过16 字母。加密行仅包含小写字母和空格 长度不超过80个字符。
输出
解密每一行并将其打印到标准输出。如果有多种解决方案,任何人都会这样做 如果没有解决方案,请用星号替换字母表中的每个字母。示例输入 6
和
迪克
简
粉扑
现货
yertlebjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn
xxxx yyy zzzz www yyyy aaa bbbb ccc dddddd示例输出
迪克和简和粉扑,斑点和yertle ...
我应该采取什么策略才能解决此问题?我正在考虑一个经典而野蛮的回溯解决方案,但我正在努力避免这种情况,直到我找到更聪明的东西。
PS:这不是与家庭作业有关,我只是想提高我的整体技能。答案 0 :(得分:9)
KeyArray将保留替换表。
从空的KeyArray开始,这是版本0
将最长的加密字与最长字典字匹配,并添加到KeyArray (如果有两个最长,选择任何),这是版本1.
解密下一个最长的加密词的一些字母。
如果某些字母匹配,请将其余字母添加到KeyArray,这是版本2.
解密下一个最长的加密词的一些字母。
重复直到所有单词都被解密。
如果版本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字时,这当然是一种非常近似的方法,但如果您有几页要解码,这将是最快的方法。
答案 4 :(得分:-1)
这是Java实现,对@CarlosGutiérrez提出的algorithm进行了进一步完善。
Crypt Kicker Algorithm and Solution, what went wrong?
改进之处在于添加单词模式以减少单词的搜索空间。例如,单词“ abc”和“她”具有相同的模式,而“ aac”和“她”不是三个不同字母的单词,不会与两个字母不同的单词匹配。
此外,该算法可以递归实现,更加直观和明智。