【问题标题】:Print all combinations of length X using recursion使用递归打印长度 X 的所有组合
【发布时间】:2019-06-09 15:22:13
【问题描述】:

示例

给定一个数组 [1,2,3] 或 [1,2,3,4] ,打印长度为 3 的所有唯一组合。

代码

public class PrintCombo {

    public void printCombo(int [] a, int [] buffer, int startIndex, int bufferIndex){

        printArray(buffer);

        if(buffer.length == bufferIndex){
            System.out.println();
            System.out.println("SOLUTION START");
            printArray(buffer);
            System.out.println("SOLUTION END");
            System.out.println();
            return;
        }
        if(startIndex == a.length){
            return;
        }

        for(int i = startIndex; i<a.length; i++){
            buffer[bufferIndex] = a[i];
            printCombo(a,buffer,i+1,bufferIndex+1);
        }
    }

    public void printArray(int [] buffer){
        for(int i = 0; i<buffer.length; i++){
            System.out.print(" "+buffer[i]);
        }
        System.out.println();
    }
}

输出

对于数组 [1,2,3] ==> 1,2,3

对于数组 [1,2,3,4] ==> 1,2,3 || 1,2,4 || 1,3,4 || 2,3,4

问题

我已经使用调试器跟踪代码 3 个小时,但我仍在努力理解递归逻辑是如何工作的。

例如,我们以数组为[1,2,3]为例。

  1. PrintCombo(a, buffer, 0, 0)
  2. buffer[0] 更新为 1
  3. 我们调用 PrintCombo(a, buffer, 1, 1)
  4. buffer[1] 更新为 2
  5. 我们调用 PrintCombo(a, buffer, 2, 2)
  6. buffer[2] 更新为 3
  7. 我们调用 PrintCombo(a, buffer, 3, 3)
  8. 由于 buffer.length == bufferIndex 我们称之为 printArray。
  9. 我们回到之前的调用

这是我迷路的地方。堆栈如何进行先前的调用?我正在努力彻底理解这种方法,因为我不喜欢记住解决方案。

我决定通过添加一个打印语句来编辑我的代码,以便在每次迭代时查看缓冲区内的内容。这是我打印的内容,例如 a = [1,2,3],缓冲区大小为 3。

 0 0 0
 1 0 0
 1 2 0
 1 2 3

SOLUTION START
 1 2 3
SOLUTION END

 1 3 3
 2 3 3
 2 3 3
 3 3 3

【问题讨论】:

  • 是否需要使用递归来解决这个问题?
  • @TimBiegeleisen 是的。我知道我们可以替代地解决它,但我真的为了我自己的利益而试图理解递归方法。
  • 递归方法不经常使用,因为大多数循环更容易调试并且不太可能进入无限期,但它们通常是这样工作的: 1. 检查退出条件的保护语句。 2. 导致对同一方法的一次或多次调用的逻辑,其中更新的参数适用于保护语句中存在的一个或多个条件
  • @AustinSchäfer 是的,理论上我明白了,但我在追踪上述问题时遇到了特别的麻烦,如果你能帮助追踪它,我将不胜感激。

标签: java recursion combinations


【解决方案1】:

当最初在printCombo 中调用for 循环时,将在每次迭代中将buffer 的第一个元素设置为所有可能的值:

[1,-,-]    // i = 0, first iteration
[2,-,-]    // i = 1, second iteration
[3,-,-]    // i = 2, ...
[4,-,-]    // i = 3, ...

对于这些迭代中的每一个,都会对printCombo 进行递归调用,以便为buffer 中的剩余元素创建所有可能的组合。例如。在第一次迭代中,[1,_,_] 被传递给 printCombo,其 for 循环现在将设置第二个元素的所有可能值:

[1,2,-]    // i = 0, first iteration in first recursive call to printCombo
[1,3,-]    // i = 1, second iteration in first recursive call to printCombo
[1,4,_]    // i = 2, ...

该过程一直持续到buffer 已满(第一个if 条件)或可能值池耗尽(第二个if 条件)。在第一种情况下,找到并打印了一个候选者。在第二种情况下,走到了死胡同。

这是buffer 随时间的演变,其中缩进级别对应于递归深度(a = [1,2,3,4] 和缓冲区大小3):

[1,-,-]       
  [1,2,-]     
    [1,2,3]   // buffer.length == bufferIndex -> printArray
    [1,2,4]   // buffer.length == bufferIndex -> printArray
  [1,3,-]   
    [1,3,4]   // buffer.length == bufferIndex -> printArray
  [1,4,-]     // startIndex == a.length -> return 
[2,-,-]   
  [2,3,-]   
    [2,3,4]   // buffer.length == bufferIndex -> printArray
  [2,4,-]     // startIndex == a.length -> return
[3,-,-]   
  [3,4,-]     // startIndex == a.length -> return
[4,-,-]       // startIndex == a.length -> return

【讨论】:

  • 我了解代码的总体目标,即递归生成所有唯一组合。但是,我在理解递归部分时遇到了困难,所以如果您可以举其中一个示例并逐步跟踪它,我将不胜感激。
  • 这种方法中递归调用的目的是询问函数是否可以与初始数组中的给定值组合。子数组[4, -, -][3, 4, -] 在此示例中会导致死胡同,因此不会添加到显示的结果中。
  • ** 在我上面的评论中,value,我的意思是buffer
  • @Dinero,我在执行过程中的每个步骤中添加了一条指示缓冲区中的值的跟踪
  • 有道理,我决定跟踪一下缓冲区的样子。我刚刚编辑了我的代码并打印了输出。它与您在上面解释的跟踪略有不同。我错过了什么吗?
【解决方案2】:

您可以考虑这种方法:每个组合都是从初始数组中删除一个值直到达到所需长度的结果。

如果你以这个数组为例[1, 2, 3, 4, 5],你将首先从它中一次删除1个值并得到以下结果数组:

[2, 3, 4, 5]    //remove index [0]
[1, 3, 4, 5]    //remove index [1]
[1, 2, 4, 5]    //remove index [2]
[1, 2, 3, 5]    //remove index [3]
[1, 2, 3, 4]    //remove index [4]

然后,您可以从它们中的每一个中删除一个值以达到您想要的长度 3。[2, 3, 4, 5] 然后会给您以下数组

[3, 4, 5]    //remove index [0]
[2, 4, 5]    //remove index [1]
[2, 3, 5]    //remove index [2]
[2, 3, 4]    //remove index [3]

等等...请注意,这 生成重复的数组,所以我的建议是通过 final 数组引用(java中的对象)并在添加之前检查结果是否已经在数组中,或者返回整个结果数组并在生成数组后删除重复项。 (虽然第一个会更节省内存)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-02
    • 2020-08-04
    • 2021-11-04
    • 2020-12-05
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    • 2012-01-30
    相关资源
    最近更新 更多