加密踢脚算法是一种众所周知的加密算法,但安全性较低。此UVa问题是基于此方法解密线。问题陈述如下:
843墓穴踢手
加密文本的一种常见但不安全的方法是置换字母。也就是说,在文本中,字母表中的每个字母始终被其他字母替换。为了确保加密是可逆的,不会用相同的字母替换两个字母。您的任务是解密几行编码的文本,假设每行使用一组不同的 替换,并且解密文本中的所有单词都来自已知单词的词典。
输入
输入包含一行,该行包含一个整数n,后跟n个小写单词,每行一个,按字母顺序排列。这n个单词构成了可能出现在解密文本中的单词词典。在字典之后是几行输入。每行如上所述进行加密。 词典中的单词数不超过1000。没有一个单词超过16个字母。加密的行仅包含小写字母和空格,并且长度不超过80个字符。
输出
解密每一行并将其打印到标准输出。如果解决方案不止一种,那么任何一种都可以。如果 没有解决方案,请用星号替换字母表中的每个字母。
样本输入
6 和 鸡巴 简 泡芙 点 yer bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn xxxx yyy zzzz www yyyy aaa bbbb ccc dddddd
样本输出
迪克,简和简,粉扑,斑点和yer子
**** * **** * **** * **** * ******
我创建了Java代码来解决它。我使用的算法如下所示。该代码似乎是正确的。它通过了我提供给它的所有测试用例,包括uDebug个测试用例。但是,当我将代码提交给UVA在线法官时,它总是返回我错误答案判决! 有人可以识别我的问题吗?代码是否有隐藏的缺陷?还是在线法官问题?!
高度赞赏任何帮助!
解决这个问题的第一个想法是对字母表中的所有字母进行置换以查找映射。但是,该解决方案在计算上非常庞大。一个更好的主意是使用 backtracking 。您将加密的单词映射到字典单词,该单词的长度和样式与加密的单词相同。 [以显示单词模式的含义:'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 "";
}
}
}
答案 0 :(得分:1)
主要问题似乎是您的程序无法解密输入的最后一行(或对其进行任何处理)。发生这种情况的原因是,循环条件c.hasNextLine()
在 之后被评估,您已经阅读了该循环迭代中要处理的行。
此外,我观察到您解决了一个与挑战所面临的问题不同的问题,尽管它是一个紧密相关的问题。你应该
解密每条 行
(加了强调),但是您实际要做的是解密每一行的单词。输入描述不保证加密的行将没有前导或尾随空白,或者相邻单词之间的空格不超过一个。如果发生任何此类情况,则您的程序不会在其输出中重现它们。
此外,尽管我倾向于将问题描述理解为字典中的单词是单独存在的,没有任何前导或尾随的空格,如果实际上不是这种情况,那么您阅读它们的方法将包括与他们的空间。只需trim()
在输入中各设置一个即可防止这种情况发生。
我最大的风格批评是:不要在循环或if
或else
块的主体周围省略花括号,即使这些主体仅由一个语句组成。这样做会使您的代码更难阅读,并为将来的维护者(包括将来的您)打下陷阱。在过去的几年中,这种遗漏至少是引起高度关注的安全问题的原因。
答案 1 :(得分:0)
在@John Bollinger的帮助下,问题终于得到解决和接受。有关我的愚蠢错误:(代码中的更多信息,请参考上面的answer。
我现在要在此处发布代码的最终版本。所做的改进包括:
代码在这里:
import java.util.*;
public class P843 {
public static void main(String[] args) {
Scanner c = new Scanner(System.in);
int dictionarySize = c.nextInt();
// this line is to flush out the input stream
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();
// remove white spaces surrounding dictionary words if there is any
words[i] = words[i].trim();
}
// get encrypted input
String line;
while (c.hasNextLine()) {
line = c.nextLine();
if (line.length() == 0) {
System.out.println();
continue;
}
// remove whitespaces
String[] eWords = line.split(" ");
for (int i = 0; i < eWords.length; i++) {
eWords[i] = eWords[i].trim();
}
// decrypt words:
String[] dWords = decryptWords(eWords, words);
// print the decrypted line
for (int i = 0; i < dWords.length; i++) {
if (i != dWords.length - 1) {
System.out.print(dWords[i] + " ");
} else {
System.out.println(dWords[i]);
}
}
}
}
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:
// for example: 'a' would be mapped to keyArray[0] and 'z' would be mapped to keyArray[25]
char[] keyArray = new char[26];
for (int i = 0; i < 26; i++) {
// initialize the keyArray to '*'
keyArray[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[i] = '*';
}
}
// decrypt line using the mapping stored in keyArray
for (int i = 0; i < eWords.length; i++) {
StringBuilder temp = new StringBuilder();
for (int j = 0; j < eWords[i].length(); j++) {
temp.append(keyArray[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[26];
System.arraycopy(keyArray, 0, originalKeyArray, 0, originalKeyArray.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 [by restoring the keyArray original values]
// and try another one
System.arraycopy(originalKeyArray, 0, keyArray, 0, keyArray.length);
}
} else // if there is no more decrypted words, then return true
return true;
}
}
// if there is no suitable mapping, return false
return false;
}
private static boolean mapWord(String eWord, String word, char[] keyArray) {
// store the current state of keyArray
char[] originalKeyArray = new char[26];
System.arraycopy(keyArray, 0, originalKeyArray, 0, keyArray.length);
// check one-to-one from the decrypted word to the dictionary word:
for (int i = 0; i < eWord.length(); i++) {
if ((keyArray[eWord.charAt(i) - 97] != word.charAt(i)
&& keyArray[eWord.charAt(i) - 97] != '*')
|| !isLetterMapped(eWord.charAt(i), word.charAt(i), keyArray)) {
// restore original array back
System.arraycopy(originalKeyArray, 0, keyArray, 0, keyArray.length);
return false;
}
// update the key array:
else {
keyArray[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[i] == letter && i != (eLetter - 97)) {
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 "";
}
}
}
如果您在本书,编程挑战书中遇到类似的问题,并且使用Java作为武器,非常欢迎您浏览并浏览my repository,我打算在其中提供更多的解决方案和技巧。 / p>
谢谢