简体   繁体   中英

Print out all subsets in an array that equal an given sum recursively

I have a weird homework that I have to write a program with a method that takes an array of non-negative integers (array elements can have repeated values) and a value sum as parameters. The method then prints out all the combinations of the elements in array whose sum is equal to sum. The weird part is, the teacher forces us to strictly follow the below structure:

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
    }

} 

We are not allow to add neither more methods nor more parameters for the current program. I have researched and understood several ways to do this recursively, but with this restriction, I don't really know how to do it. Therefore, I appreciate if you guys help me out.

EDIT: The array can have repeated elements. Here's an example run of the program.

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

Outputs:

(1, 2) // 1st and 3rd element

(1, 2) // 1st and 4th element

(3) // 2nd element

As the printCombinations() method accepts the integer array as parameter and you are not allowed to add any additional methods. I couldn't think of Recursion without adding an additional method.

Here is a solution, let me know if this helps. And this is not the best way!

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;
    }
}

This is extremely suited for recursive algorithm.

Think about function, let's call it fillRemaining , that gets the current state of affairs in parameters. For example, usedItems would be a list that holds the items that were already used, availableItems would be a list that holds the items that haven't been tried, currentSum would be the sum of usedItems and goal would be the sum you are searching for.

Then, in each call of fillRemaining , you just have to walk over availableItems and check each one of them. If currentSum + item == goal , you have found a solution. If currentSum + item > goal , you skip the item because it's too large. If currentSum + item < goal , you add item to usedItems and remove it from availableItems , and call fillRemaining again. Of course, in this call currentSum should also be increased by item .

So in printCombinations , you initialize availableItems to contain all elements of arr , and usedItems to empty list. You set currentSum to 0 and goal to sum , and call fillRemaining . It should do the magic.

With the restriction of not being able to add any other methods or parameters, you can also make fields for availableItems , usedItems , currentSum and goal . This way, you don't have to pass them as parameters, but you can still use them. The fields will have to be static, and you would set them in main as described above.

If neither adding fields is allowed, then you have to somehow simulate nested loops with variable depth. In effect, this simulates what would otherwise be passed via stack, but the algorithm is still the same.

In effect, this algorithm would do a depth-first search of (pruned) tree of all possible combinations. Beware however, that there are 2^n combinations, so the time complexity is also O(2^n).

I think that all algorithms which can be solved with recursion can also be solved with stacks instead of recursion (see solution below). But very often it is easier to solve the problems with recursion before attempting the stack based solutions.

My recursive take on this problems would be in Java something like this:

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);
    }
}

This function you can call like this:

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

And it will print this:

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

Basically it goes through all possible sets in this array and then filters those out which have the sum of 3 in this case. It is not great, there are for sure better, more efficient ways to do this. But my point here is simply to show that you can convert this algorithm to a stack based implementation.

Here it goes how you can implement the same algorithm using stacks instead of recursion:

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(",")));
        }
    }
}

This method can be called like this:

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

And it outputs also:

1,2
1,2
3

How I came to this conversion of a recursive to a stack based algorithm:

If you observe the positions in the acc array on the first algorithm above, then you will see a pattern which can be emulated by a stack. If you have an initial array with 4 elements, then the positions which are in the acc array are always these:

[]
[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]

There is a pattern here which can easily be emulated with stacks:

The default operation is always to push into the stack, unless you reach the last position in the array. You push first 0 which is the first position in the array. When you reach the last position of the array, you pop once from the array and then pop again and a second popped item which you push back to the stack - incremented by one.

If the stack is empty you break the loop. You have gone through all possible combinations.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM