打印出一个递归等于给定和的数组中的所有子集

时间:2017-11-16 10:58:20

标签: java arrays recursion arraylist

我有一个奇怪的作业,我必须用一个方法编写一个程序,该方法采用非负整数数组(数组元素可以有重复值)和值和作为参数。然后该方法打印出数组中元素的所有组合,其总和等于和。奇怪的是,老师强迫我们严格遵循以下结构:

public class Combinations {

    public static void printCombinations(int[] arr, int sum) {
       // Body of the method
    }

    public static void main(String[] args) {
       // Create 2-3 arrays of integers and 2-3 sums here then call the above
       // method with these arrays and sums to test the correctness of your method
    }

} 

我们不允许为当前程序添加更多方法或更多参数。我已经研究并了解了几种递归方式,但有了这个限制,我真的不知道怎么做。因此,如果你们帮助我,我感激不尽。

编辑:数组可以有重复的元素。这是该程序的一个示例运行。

arr = {1, 3, 2, 2, 25} and sum = 3

输出:

  

(1,2)//第1和第3个元素

     

(1,2)//第1和第4个元素

     

(3)//第二元素

4 个答案:

答案 0 :(得分:1)

由于 printCombinations()方法接受整数数组作为参数,因此不允许添加任何其他方法。如果不添加额外的方法,我就无法想到递归。

这是一个解决方案,请告诉我这是否有帮助。这不是最好的方式!

public static void main( String[] args ) throws Exception {
    int arr[] = {1, 3, 2, 2, 25, 1, 1};
    int sum = 8;
    printCombinations(arr, sum);
}

public static void printCombinations(int arr[], int sum){
    int count = 0;
    int actualSum = sum;
    while (count < arr.length) {
        int j = 0;
        int arrCollection[] = new int[arr.length];
        for (int k = 0; k < arrCollection.length; k++){
            arrCollection[k] = -99; // as the array can contain only +ve integers
        }
        for (int i = count; i < arr.length; i++) {
            sum = sum - arr[i];
            if (sum < 0){
                sum = sum + arr[i];
            } else if (sum > 0){
                arrCollection[j++] = arr[i];
            } else if (sum == 0){
                System.out.println("");
                arrCollection[j++] = arr[i];
                int countElements = 0;
                for (int k = 0; k < arrCollection.length; k++){
                    if (arrCollection[k] != -99) {
                        countElements++;
                        System.out.print(arrCollection[k] + " ");
                    }
                }
                if (countElements == 1){
                    i = arr.length -1;
                }
                sum = sum + arr[i];
                j--;
            }
        }
        count++;
        sum = actualSum;
    }
}

答案 1 :(得分:0)

这非常适合递归算法。

考虑一下函数,让它调用它fillRemaining,它可以获取参数中的当前事务状态。例如,usedItems将是一个包含已使用项目的列表,availableItems将是一个列表,其中包含尚未尝试的项目,currentSum将是usedItemsgoal的总和将是您要搜索的总和。

然后,在fillRemaining的每次通话中,您只需要走过availableItems并检查其中的每一个。如果currentSum + item == goal,您已找到解决方案。如果currentSum + item > goal,则跳过该项,因为它太大了。如果currentSum + item < goal,您将item添加到usedItems并将其从availableItems中移除,然后再次致电fillRemaining。当然,在此次通话中currentSum也应增加item

因此,在printCombinations中,您将availableItems初始化为包含arrusedItems的所有元素以清空列表。您将currentSum设为0,将goal设为sum,然后致电fillRemaining。它应该是神奇的。

由于无法添加任何其他方法或参数,您还可以为availableItemsusedItemscurrentSum和{设置字段 {1}}。这样,您不必将它们作为参数传递,但您仍然可以使用它们。这些字段必须是静态的,您可以如上所述在goal中设置它们。

如果既不允许添加字段,则必须以某种方式模拟具有可变深度的嵌套循环。实际上,这模拟了否则将通过堆栈传递的内容,但算法仍然是相同的。

实际上,该算法将对所有可能组合的(修剪)树进行深度优先搜索。但要注意,有2 ^ n个组合,因此时间复杂度也是O(2 ^ n)。

答案 2 :(得分:0)

我认为所有可以通过递归求解的算法也可以用堆栈而不是递归来解决(参见下面的解决方案)。但在尝试基于堆栈的解决方案之前,通常更容易解决递归问题。

我对Java问题的递归看法将是这样的:

public static void printCombinations(int[] array, int pos, int sum, int[] acc) {
    if (Arrays.stream(acc).sum() == sum) {
        System.out.println(Arrays.toString(acc));
    }
    for (int i = pos + 1; i < array.length; i++) {
        int[] newAcc = new int[acc.length + 1];
        System.arraycopy(acc, 0, newAcc, 0, acc.length);
        newAcc[acc.length] = array[i];
        printCombinations(array, i, sum, newAcc);
    }
}

你可以这样调用这个函数:

printCombinations(new int[]{1, 3, 2, 2, 25}, -1, 3, new int[]{});

它将打印出来:

[1, 2]
[1, 2]
[3]

基本上它遍历此数组中的所有可能的集合,然后过滤那些在这种情况下总和为3的集合。这不是很好,确实有更好,更有效的方法来做到这一点。但我的观点仅仅是为了表明您可以将此算法转换为基于堆栈的实现。

下面介绍如何使用堆栈而不是递归来实现相同的算法:

public static void printCombinationsStack(int[] array, int sum) {
    Stack<Integer> stack = new Stack<>();
    stack.push(0);
    while (true) {
        int i = stack.peek();
        if (i == array.length - 1) {
            stack.pop();
            if (stack.isEmpty()) {
                break;
            }
            int last = stack.pop();
            stack.push(last + 1);
        } else {
            stack.push(i + 1);
        }
        if (stack.stream().map(e -> array[e]).mapToInt(Integer::intValue).sum() == sum) {
            System.out.println(stack.stream().map(e -> Integer.toString(array[e]))
                    .collect(Collectors.joining(",")));
        }
    }
}

可以像这样调用此方法:

printCombinationsStack(new int[]{1, 3, 2, 2, 25}, 3);

它还输出:

1,2
1,2
3

我是如何将递归转换为基于堆栈的算法:

如果您在上面的第一个算法中观察acc数组中的位置,那么您将看到一个可以由堆栈模拟的模式。如果你有一个包含4个元素的初始数组,那么acc数组中的位置总是如下:

[]
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 3]
[0, 2]
[0, 2, 3]
[0, 3]
[1]
[1, 2]
[1, 2, 3]
[1, 3]
[2]
[2, 3]
[3]

这里有一个模式可以很容易地用堆栈模拟:

默认操作始终是推入堆栈,除非您到达阵列中的最后一个位置。你先推0,这是数组中的第一个位置。当你到达数组的最后一个位置时,你从数组中弹出一次,然后再次弹出,然后弹出第二个弹出的项目,你将其推回到堆栈 - 增加1。

如果堆栈为空,则打破循环。你已经完成了所有可能的组合。

答案 3 :(得分:0)

似乎重复,请通过以下链接获取具有确切代码复杂性详细信息的正确解决方案 find-a-pair-of-elements-from-an-array-whose-sum-equals-a-given-number