问题描述:
密码踢球算法
是一种众所周知的加密算法,但不太安全。这个UVa问题是基于此方法解密行。问题陈述如下:
843地下室踢球者
替换,并且解密文本中的所有单词都来自已知单词的字典。
输入
输入由一行组成,该行包含一个整数n,后跟n个小写单词,每行一个,按字母顺序排列。这n个单词组成了可能出现在解密文本中的单词词典。字典后面是几行输入。每一行按上述方式加密。
字典里的单词不超过1000个。没有超过16个字母的单词。加密行只包含小写字母和空格,长度不超过80个字符。
输出
解密每一行并打印到标准输出。如果有多个解决方案,任何一个都可以。如果
没有解决办法,用星号替换字母表中的每个字母。
6个
和
家伙
泡芙
地点
bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn公司
xxxx年yyy zzzz www年yyy aaa bbbb ccc dddddd
样本输出
迪克、简、波夫、斯波特和耶特尔
****
* **** *
****
******
出什么事了?
我已经创建了一个Java代码来解决这个问题。我使用的算法如下所示。代码似乎是正确的。它通过了我提供给它的所有测试用例,包括
uDebug
测试用例。但是,当我把代码提交给UVA在线评委时,
它总是给我错误的答案
!
有人能查出我的问题吗?代码是否有隐藏的缺陷?还是网上法官的问题?!
算法描述:
解决这个问题的第一个想法是将字母表中的所有字母排列,以便找到一个映射。然而,这个解决方案的计算量很大。更好的办法是
回溯
. 将加密的单词映射到与加密单词具有相同长度和模式的字典单词。[为了说明单词模式的含义:'abc'可能映射到'her',但是它不能映射到'see',因为模式不同,“三个不同的字母单词不能映射到两个不同的字母单词”我从
this discussion
]. 如果找到第一个单词的映射,则移动到下一个单词并映射它。如果第二个单词没有解决方案,则返回到第一个单词并尝试另一个映射,依此类推。如果没有合适的映射到第一个单词,则声明没有解决方案。对于映射,我首先映射最长的单词,因为它们很难映射。
代码:
这是我的密码。我试着把重要的台词注释掉。不过,如果你觉得有什么困惑,尽管问,我会详细解释。谢谢,
import java.util.*;
public class P843 {
public static void main(String[] args) {
Scanner c = new Scanner(System.in);
int dictionarySize = c.nextInt();
c.nextLine();
// get the dictionary from the input stream:
String[] words = new String[dictionarySize];
for (int i = 0; i < words.length; i++)
words[i] = c.nextLine();
// get encrypted input
String line = c.nextLine();
while (c.hasNextLine()) {
if (line.length() == 0) {
System.out.println();
line = c.nextLine();
continue;
}
ArrayList<String> eWordsList = new ArrayList<>();
for(String eWord : line.split(" "))
if(eWord.length() != 0)
eWordsList.add(eWord);
String[] eWords = eWordsList.toArray(new String[0]);
String[] dWords = decryptWords(eWords, words);
for (int i = 0; i < dWords.length; i++)
if (i != dWords.length - 1)
System.out.print(dWords[i] + " ");
else
System.out.println(dWords[i]);
line = c.nextLine();
}
}
private static String[] decryptWords(String[] eWords, String[] words) {
String[] dWords = new String[eWords.length];
// get copy of the dWords so that the original version not destroyed
String[] eWordsCopy = Arrays.copyOf(eWords, eWords.length);
// sort by length from the greatest to the smallest
Arrays.sort(eWordsCopy, new Comparator<String>() {
@Override
public int compare(String w1, String w2) {
return Integer.compare(w2.length(), w1.length());
}
});
// initialize a key array to hold decrypted letters:
char[][] keyArray = new char[2][26];
for (int i = 0; i < 26; i++) {
keyArray[0][i] = (char) ('a' + i);
keyArray[1][i] = '*';
}
// restore keyArray original values if there is no solution to all words
if (!matchWords(words, eWordsCopy, keyArray))
for (int i = 0; i < 26; i++) {
keyArray[0][i] = (char) ('a' + i);
keyArray[1][i] = '*';
}
for (int i = 0; i < eWords.length; i++) {
StringBuilder temp = new StringBuilder();
for (int j = 0; j < eWords[i].length(); j++)
temp.append(keyArray[1][eWords[i].charAt(j) - 97]);
dWords[i] = temp.toString();
}
return dWords;
}
private static boolean matchWords(String[] words, String[] eWords, char[][] keyArray) {
ArrayList<String> promisingWords = new ArrayList<>();
String eWord = eWords[0];
// store the current state of keyArray
char[][] originalKeyArray = new char[2][26];
for (int i = 0; i < keyArray.length; i++)
if (keyArray[i].length >= 0) System.arraycopy(keyArray[i], 0, originalKeyArray[i], 0, keyArray[i].length);
// get promising words that may match
for (String word : words)
if (word.length() == eWord.length()
&& wordPattern(word).equals(wordPattern(eWord)))
promisingWords.add(word);
for (String word : promisingWords) {
if (mapWord(eWord, word, keyArray)) {
if (eWords.length > 1) {
// recursive call:
if (matchWords(words, Arrays.copyOfRange(eWords, 1, eWords.length), keyArray))
return true;
else {
// remove the word from the dictionary to try another one
for (int i = 0; i < keyArray.length; i++)
if (keyArray[i].length >= 0)
System.arraycopy(originalKeyArray[i], 0, keyArray[i], 0, keyArray[i].length);
}
}
// if there are now decrypted words, then return true
else
return true;
}
}
// if there is no word mapped, then return false
return false;
}
private static boolean mapWord(String eWord, String word, char[][] keyArray) {
// store the current state of keyArray
char[][] originalKeyArray = new char[2][26];
for (int i = 0; i < keyArray.length; i++)
if (keyArray[i].length >= 0) System.arraycopy(keyArray[i], 0, originalKeyArray[i], 0, keyArray[i].length);
// check one-to-one from the decrypted word to the dictionary word:
for (int i = 0; i < eWord.length(); i++)
if ((keyArray[1][eWord.charAt(i) - 97] != word.charAt(i)
&& keyArray[1][eWord.charAt(i) - 97] != '*')
|| !isLetterMapped(eWord.charAt(i), word.charAt(i), keyArray)) {
// restore original array back
for (int j = 0; j < keyArray.length; j++)
if (keyArray[j].length >= 0)
System.arraycopy(originalKeyArray[j], 0, keyArray[j], 0, keyArray[j].length);
return false;
}
// update the key array:
else
keyArray[1][eWord.charAt(i) - 97] = word.charAt(i);
return true;
}
private static boolean isLetterMapped(char eLetter, char letter, char[][] keyArray) {
for (int i = 0; i < 26; i++)
if (keyArray[1][i] == letter && keyArray[0][i] != eLetter)
return false;
return true;
}
// generate a word pattern
private static String wordPattern(String word) {
if (word.length() > 0) {
StringBuilder mapped = new StringBuilder();
int count = 0;
HashMap<Character, Character> mapping = new HashMap<>();
for (int i = 0; i < word.length(); i++)
if (!mapping.containsKey(word.charAt(i)))
mapping.put(word.charAt(i), (char) (48 + count++));
for (int i = 0; i < word.length(); i++)
mapped.append(mapping.get(word.charAt(i)));
return mapped.toString();
} else {
return "";
}
}
}