【问题标题】:Find all permutations of 64 byte array?查找 64 字节数组的所有排列?
【发布时间】:2015-06-15 04:59:32
【问题描述】:

我的目标是找到一个 64 字节数组的所有排列,并为每个排列检查在执行函数 F 后是否等于给定的字节数组。

考虑一个小规模的例子:假设我有 1234,我想 生成 4 位数字 _ _ _ _ 的所有排列并检查 每次如果它等于 1234

我的第一个想法是实现一个递归函数来生成排列。但是考虑到大小,栈会溢出。

任何有效的方法来生成所有排列?鉴于 Java 有大量的库?

【问题讨论】:

  • 我认为,如果您尝试实际生成所有这些排列并将它们保存在内存中,那么您将遇到堆内存问题,而不是堆栈大小,其中 64 的深度并不过分。
  • 有64个! ~ 1.26886932e89 种可能的排列。您将无法全部看完。
  • @Shashank,我打算发表同样的评论,但请看问题的黄色部分。这实际上似乎是OP所要求的。 OP中的“排列”一词可能不正确。
  • @RealSkeptic 那么我很困惑,不幸的是......要么是笛卡尔积的 256^64,要么是 64!在对给定的 64 字节数组进行重复排列的情况下。无论哪种方式...合理计算都太多了。
  • @Shashank 确实如此。否则很容易通过暴力破解任意密码。

标签: java arrays algorithm permutation


【解决方案1】:

至于非递归,这​​个答案可能会有所帮助:Permutation algorithm without recursion? Java

至于简单的例子,这是我制定的递归解决方案:

public class Solution {
    public List<List<Integer>> permute(int[] num) {
        boolean[] used = new boolean[num.length];
        for (int i = 0; i < used.length; i ++) used[i] = false;

        List<List<Integer>> output = new ArrayList<List<Integer>>();
        ArrayList<Integer> temp = new ArrayList<Integer>();

        permuteHelper(num, 0, used, output, temp);

        return output;

    }

    public void permuteHelper(int[] num, int level, boolean[] used, List<List<Integer>> output, ArrayList<Integer> temp){

        if (level == num.length){
            output.add(new ArrayList<Integer>(temp));
        }
        else{

            for (int i = 0; i < num.length; i++){
                if (!used[i]){
                    temp.add(num[i]);
                    used[i] = true;
                    permuteHelper(num, level+1, used, output, temp);
                    used[i] = false;
                    temp.remove(temp.size()-1);

                }
            }

        }

    }
}

编辑:10 似乎是递归方法在合理时间内完成的最大输入。

输入长度为 10 的数组:

Permutation execution time in milliseconds: 3380

【讨论】:

  • 您是否尝试过使用适度的数字,例如 int[15] 左右?
  • 不直接,不。我认为它被 leetcode 在线评委接受了。现在我很好奇,现在就去试试。
【解决方案2】:

编辑: 我快速搜索了可能的实现,并发现了一个算法建议作为另一个问题的答案的一部分。

为了您的方便,下面是由https://stackoverflow.com/users/2132573/jon-b 开发的代码。 它正确地创建并打印了整数列表的所有排列。您可以轻松地对数组使用相同的逻辑(感谢 jon-b !!)。

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class HelloWorld {
    public static int factorial(int x) {
            int f = 1;
            while (x > 1) {
                    f = f * x;
                    x--;
            }
            return f;
    }

    public static List<Integer> permute(List<Integer> list, int iteration) {
            if (list.size() <= 1) return list;
            int fact = factorial(list.size() - 1);
            int first = iteration / fact;
            List<Integer> copy = new ArrayList<Integer>(list);
            Integer head = copy.remove(first);
            int remainder = iteration % fact;
            List<Integer> tail = permute(copy, remainder);
            tail.add(0, head);
            return tail;
    }

    public static void main(String[] args) throws IOException {
            List<Integer> list = Arrays.asList(4, 5, 6, 7);
            for (int i = 0; i < 24; i++) {
                System.out.println(permute(list, i));
            }
    }
}

我已经在http://www.tutorialspoint.com/compile_java8_online.php 中对其进行了测试,并且效果很好。

希望有帮助!

【讨论】:

  • 感谢您的来信。我现在正在检查它!
  • 我建议你尝试用 15 个整数的列表来测试它。不要忘记将循环限制更新为 15!主要。 ;-)
  • @RealSkeptic:为你的收获点赞!我已经对其进行了 15 年的测试,并且 OMG……正在努力……
  • @RealSkeptic:数组是 64 字节,我假设它最多有 16 个整数。所以在某些时候我们将需要 16 个! = 2.092279e+13。在 int 的边界之外,但在 long 的内部。你有什么建议吗?
  • 啊,但是您不能在整数中使用四个字节的块,因为您还需要置换相同整数中的字节。所以你最终会得到 64!。也就是说,如果我们假设 OP 意味着排列(参见 cmets 到 OP)。即使一个排列需要 1 纳秒来运行,也需要大约 10⁷² 年才能运行。那么,我的建议呢?离开吧。
【解决方案3】:

如果我理解正确,你需要生成所有的 64! 64字节数组的排列,即:

64! = 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000排列!

如果每个排列和比较都需要一毫秒(最坏的时间场景)来计算,您需要:

4023558225072430368576654912961741527234446859923426946943236123009091331506849.3150684931506849315 在一台机器上计算它们! (如果每个排列都需要 100 毫秒,那么这个怪物的 100 分之一)。

因此,您应该通过应用一些启发式方法来减少问题的搜索空间,而不是天真地列出所有可能的解决方案。

在您将搜索空间缩小为更易于处理的数字后,例如:14! (“一毫秒”情况下的计算时间为 2 年),您可以使用 Factoradics(实现 here)将计算拆分到多台机器上,计算每台机器的开始和结束排列,然后使用每个节点中的以下代码(Knuth's L-algorithm 的实现)在每台机器中搜索解决方案:

public class Perm {
    private static byte[] sequenceToMatch;
    private static byte[] startSequence;    
    private static byte[] endingSequence;        
    private static final int SEQUENCE_LENGTH = 64;

    public static void main(String... args) {
        final int N = 3;

        startSequence = readSequence(args[0]);
        endingSequence = readSequence(args[1]);
        sequenceToMatch = readSequence(args[2]);                

        permutations();
    }    

    private static boolean sequencesMatch(byte[] s1, byte[] s2) {
        for (int i = 0; i < SEQUENCE_LENGTH; i++) {
            if (s1[i] != s2[i]) {
                return false;
            }
        }
        return true;
    }

    private static byte[] readSequence(String argument) {
        String[] sBytes = argument.split(",");
        byte[] bytes = new byte[SEQUENCE_LENGTH];
        int i = 0;
        for (String sByte : sBytes) {
            bytes[i++] = Byte.parseByte(sByte, 10);
        }
        return bytes;
    }

    private static void swap(byte[] elements, int i, int j) {
        byte temp = elements[i];
        elements[i] = elements[j];
        elements[j] = temp;
    }

    /**
     * Reverses the elements of an array (in place) from the start index to the end index 
     */
    private static void reverse(byte[] array, int startIndex, int endIndex) {
        int size = endIndex + 1 - startIndex;
        int limit = startIndex + size / 2;
        for (int i = startIndex; i < limit; i++) {
            // swap(array, i, startIndex + (size - 1 - (i - startIndex)));
            swap(array, i, 2 * startIndex + size - 1 - i);
        }
    }

    /**
     * Implements the Knuth's L-Algorithm permutation algorithm 
     * modifying the collection in place
     */
    private static void permutations() {
        byte[] sequence = startSequence;

        if (sequencesMatch(sequence, sequenceToMatch)) {
            System.out.println("Solution found!");
            return;
        }

        // For every possible permutation 
        while (!sequencesMatch(sequence, endingSequence)) {

            // Iterate the array from right to left in search 
            // of the first couple of elements that are in ascending order
            for (int i = SEQUENCE_LENGTH - 1; i >= 1; i--) {
                // If the elements i and i - 1 are in ascending order
                if (sequence[i - 1] < sequence[i]) {
                    // Then the index "i - 1" becomes our pivot index 
                    int pivotIndex = i - 1;

                    // Scan the elements at the right of the pivot (again, from right to left)
                    // in search of the first element that is bigger
                    // than the pivot and, if found, swap it
                    for (int j = SEQUENCE_LENGTH - 1; j > pivotIndex; j--) {
                        if (sequence[j] > sequence[pivotIndex]) {
                            swap(sequence, j, pivotIndex);
                            break;
                        }
                    }

                    // Now reverse the elements from the right of the pivot index
                    // (this nice touch to the algorithm avoids the recursion)
                    reverse(sequence, pivotIndex + 1, SEQUENCE_LENGTH - 1);
                    break;
                }
            }

            if (sequencesMatch(sequence, sequenceToMatch)) {
                System.out.println("Solution found!");
                return;
            }
        }
    }
}

【讨论】:

  • 您的答案当然是迄今为止最好的。但我确实相信 OP 实际上意味着 64 字节的所有组合,而不是特定的排列(参见 OP 中的黄色部分)。所以这将是 256⁶⁴ 组合。当然,您的建议仍然适用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-31
  • 2015-08-08
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2010-09-23
相关资源
最近更新 更多