problems, solutions, quirky behaviors

Main menu

Subset Sum Problem again

Not long ago someone sent me an email about my SubsetSum problem post, asking how to reconstruct the set of numbers that were used to create the sum. This is pretty straightforward, until you consider scenarios in which there is more than one correct subset and you want a specific one or all of them.

But, first thing’s first. Let’s consider the easy case – we assume there is only one correct result. For example consider the follwing set { 15, 20, 35, 40 } and the sum of 90 (15+35+40). Using the standard version of the algorithm, we’ll receive the table as follows (top row has values instead of indexes to increase readability, also, I skip non-relevant rows:

X

0

15

20

35

40

0

true

true

true

true

true

15

false

true

true

true

true

20

false

false

true

true

true

35

false

false

true

true

true

40

false

false

false

false

true

50

false

false

false

true

true

55

false

false

false

false

true

90

false

false

false

false

true

And so, to reconstruct values used, we’re going to determine which number was used to create each subset.

we start with our final value of 90, the first column in which we have ‘true’ is the last one, representing the value ’40’. We add 40 to the result set and substract this value from our final sum.And so recursively, we search for numbers until we reach 0. Starting from 90 we’ll find 40, then search for 90-40 = 50, 50 is true for column representing 35, so we’ll look for 50-35 = 15, we’ll find 15 in the second column and will not search for 15-15 = 0 (and we’re done)

The code can look something like this:

Determing the numbers used to create sum

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

boolean[][]table=newboolean[sum+1][numbers.length+1];

Arrays.fill(table[0],true);

for(inti=1;i<=sum;i++){

for(intj=1;j<=numbers.length;j++){

table[i][j]=table[i][j-1];

if(numbers[j-1]<=i){

table[i][j]|=table[i-numbers[j-1]][j-1];

}

}

}

intcurrentSum=sum;

ArrayList<Integer>l=newArrayList<>();

while(currentSum>0){

for(intj=0;j<table[currentSum].length;j++){

if(table[currentSum][j]){

l.add(numbers[j-1]);

currentSum-=numbers[j-1];

break;

}

}

}

So what will happen if we want to find out what numbers to use to create 35? It can be either 15+20, or just 35. Algorithm in current form will return answer using as little numbers as possible , so it will be just 35. What to do to make it return both ?

Well, we need to remember in how many ways we can create any number from previous numbers. To do this, we’ll use int[][] instead of boolean[][] and increment the value each time we can find a new way to create a number

So the new table will look like this

X

0

15

20

35

40

0

1

1

1

1

1

15

0

1

1

1

1

20

0

0

1

1

1

35

0

0

1

2

2

We can see from this table that 35 can be created from either 35 (leading to 35-35 = 0 and end of algorithm) or from 20 (leading to 35-20 = 15).

We’ll need to branch on every such occasion, so it’s easiest to use recursion. It may look something like this:

getting all possible subsets

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

publicstaticvoidprintAll(int[]numbers,int[][]t,intsum,

List<Integer>l){

if(sum==0){

System.out.println(l);

}else{

intcurrent=0;

for(inti=0;i<t[sum].length;i++){

if(t[sum][i]!=current){

current=t[sum][i];

intvalue=numbers[i-1];

l.add(value);

printAll(numbers,t,sum-value,l);

l.remove(l.size()-1);

}

}

}

}

publicstaticvoidgenerateSum(int[]numbers,intsum){

int[][]table=newint[sum+1][numbers.length+1];

Arrays.fill(table[0],1);

for(inti=1;i<=sum;i++){

for(intj=1;j<=numbers.length;j++){

table[i][j]=table[i][j-1];

if(numbers[j-1]<=i

&&table[i-numbers[j-1]][j-1]!=0){

table[i][j]++;

}

}

}

printAll(numbers,table,sum,newArrayList<Integer>());

}

There are two things I believe are worth noticing:

1. It’s relatively easy to apply the same optimizations as in the original post – directly if we have unique sums or more indirectly by creating lists of starting positions if we have multiple subsets giving the same sum.

2.It’s also relatively easy to count all possible subsets without enumerating them (just one line change, instead of table[i][j]++ do table[i][j] += table[i – numbers[j – 1]][j – 1];, table[sum].length – 1] will hold result after algorithm is done)

There is a lot of fun in modifying this to achieve results like ‘subset using least numbers’ or ‘subset minimizing the use of even numbers’, etc. most of which require just small changes. I encourage you to to play with it as a fun exercise.