代码之家  ›  专栏  ›  技术社区  ›  Maged Saeed

密码攻击算法和解决方案,出了什么问题?

  •  1
  • Maged Saeed  · 技术社区  · 7 年前

    问题描述:

    密码踢球算法 是一种众所周知的加密算法,但不太安全。这个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 "";
        }
    }
    }
    
    2 回复  |  直到 7 年前
        1
  •  1
  •   John Bollinger    7 年前

    主要问题似乎是程序无法解密(或对最后一行输入执行任何操作)。这是因为循环条件 c.hasNextLine() 被评估 您已经阅读了循环迭代中要处理的行。

    另外,我注意到你解决了一个与挑战不同的问题,尽管这是一个密切相关的问题。你应该

    线

    (强调了一下),但实际上你要做的是解密 文字

    此外,虽然我倾向于将问题描述理解为字典中的单词单独在它们的行上,没有任何前导或尾随的空格,但如果事实并非如此,那么阅读它们的方法将包括它们的空格。只要 trim() 每个输入。

    我最大的风格批评是: if else ,即使这些实体只有一个语句。这样做会使您的代码更难阅读,并为将来的维护人员(包括将来的您)设置陷阱。在过去几年里,这种疏漏至少导致了一个引人注目的安全问题。

        2
  •  0
  •   Maged Saeed    7 年前

    answer 以上是关于我愚蠢错误的更多细节:(在代码中)。

    我现在发布代码的最终版本 . 所作的改进包括:

    • 修复从输入流读取时的错误。
    • 将keyrarray从2D数组更改为1D数组。
    • 通过添加花括号和注释更多的行来美化代码。

    代码在这里:

    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 在那里我打算提出更多的解决方案和技巧。