2

Given the following Input

10 4 3 5 5 7

Where

10 = Total Score

4 = 4 players

3 = Score by player 1

5 = Score by player 2

5 = Score by player 3

7 = Score by player 4

I am to print players who's combine score adds to total so output can be 1 4 because player 1 + player 4 score = 3 + 7 -> 10 or output can be 2 3 because player 2 + player 3 score = 5 + 5 -> 10

So it is quite similar to a subset sum problem. I am relatively new to dynamic programing but after getting help on stackoverflow and reading dynamic programing tutorials online and watch few videos online for past 3 days. The following code i have come with so far.

class Test
{
    public static void main (String[] args) throws java.lang.Exception
    {
        int[] test = {3,5,5,7};
        getSolution(test,4,10);
    }

    //pass total score, #of players (size) and the actual scores by each player(arr)
    public static int getSolution(int[] arr,int size, int total){


        int W = total;
        int n = size;
        int[][] myArray = new int[W+1][size+1];

        for(int i = 0; i<size+1; i++)
        {
            myArray[i][0] = 1;
        }
        for(int j =1; j<W+1; j++)
        {
            myArray[0][j] = 0;
        }

        for(int i =1; i<size+1; i++)
        {
            for(int x=1; x<W+1; x++)
            {
                if(arr[i] < x)
                {
                    myArray[i][x] = myArray[i-1][x];
                }
                else
                {
                    myArray[i][x] = myArray[i-1][x-arr[i]];
                }
            }
        }

        return myArray[n][W];
    }

}

For some reason i am not getting expected result. I have been trying to find bug in this issue for past 7+ hours without 0 success. I would highly appreciate it if someone can help fix the issue to get the desired result.

Also please forgive my English it is not my first language.

Update Also i do not need to print all possible combinations that equal the score. I can print any combination that equals the score and it will be fine.

user1010101
  • 2,062
  • 7
  • 47
  • 76
  • `main` is calling with a size of 3, not 4. Is this intended? – Nathan Tuggy Mar 05 '15 at 00:16
  • First: Your English is great! Second, are we constrained in that only pairs of student's scores will be considered? I.E. If the input was `10 2 3 4 1 4`, could we combine `2 3 4 1` to get `10`? (4 students) – AndyG Mar 05 '15 at 00:16
  • mistake @NathanTuggy oops just realized...i will fix right away. – user1010101 Mar 05 '15 at 00:20
  • @AndyG Thank you! yes you can combine scores of as many players as you want as long as they add up to the total – user1010101 Mar 05 '15 at 00:20
  • @user2733436: I'm afraid your problem *is* Subset Sum. Worst case exact algorithm still has exponential time complexity. I.e. As a beginner you'll be better off just trying to generate every possible subset of students, and then evaluating the sum of that subset. – AndyG Mar 05 '15 at 00:24
  • @AndyG The maximum total score that can be given is 1000. Does that make a difference? – user1010101 Mar 05 '15 at 00:52
  • @AndyG Also i do not need to print all possible combinations i can print any combination that equals the total score – user1010101 Mar 05 '15 at 00:55
  • @AndyG please see my edit, also let me know if you require any more clarifications. I am still trying to solve this but without any luck. almost entire day i have tried now. would highly appreciate if u can provide any help. – user1010101 Mar 05 '15 at 01:30
  • @user2733436: What I'm saying is that you don't need to print all possible combinations. Evaluate all possible combinations, and then if one combination produces the desired sum, stop and print that one. It's a naive, simplistic, brute-force algorithm, but is a good starting point. – AndyG Mar 05 '15 at 01:34
  • @AndyG Is it not possible to solve it using my approach? I mean can you point out where i have gone wrong possibly... – user1010101 Mar 05 '15 at 01:36
  • @user2733436: Correct, your approach is not guaranteed to produce a solution. Code-wise, one thing you have to remember is that arrays are 0-indexed. So `if (arr[i] < x)` is never going to check `arr[0]`, and the first student can never be included in any solution. – AndyG Mar 05 '15 at 02:09
  • @AndyG i see your point... I am trying now but also i know you must not be having much time but if you can find some time to provide some working code it would be awesome. then i can even trace each line by line and understand it and study it so in future i can solve all such problems. again thanks. – user1010101 Mar 05 '15 at 02:23
  • @user2733436: I've posted some code, but word to the wise, you should study how to generate a power set via algorithm on pen and paper w/ numbers. It is surprisingly difficult unless you know the solution (and then it's kind of easy). And then you can see this code and understand it. If you choose to copy-paste and then forget, you'll have learned nothing. – AndyG Mar 05 '15 at 04:51

4 Answers4

2
public List<Integer> findSubsetWithSum(int[] score, int totalScore)
{
    int players = score.length;

    int[] cameFrom = new int[totalScore+1];
    int[] pickedPlayer = new int[totalScore+1];
    for (int s = 0; s <= totalScore; s++)
    {
        cameFrom[s] = -1;
        pickedPlayer[s] = -1;
    }
    cameFrom[0] = 0;
    for (int p = 0; p < players; p++)
    {
        for (int s = score[p]; s <= totalScore; s++)
        {
            if (cameFrom[s - score[p]] >= 0)
            {
                cameFrom[s] = s - score[p];
                pickedPlayer[s] = p + 1;
            }
        }
    }
    List<Integer> picked = new ArrayList<Integer>();
    for (int s = totalScore; s > 0 && cameFrom[s] >= 0; s = cameFrom[s])
    {
        picked.add(pickedPlayer[s]);
    }
    return picked;
}
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
1

Your problem is in this part of the code

            if(arr[i] < x)
            {
                myArray[i][x] = myArray[i-1][x];
            }
            else
            {
                myArray[i][x] = myArray[i-1][x-arr[i]];
            }

You have two situations

  1. (inside if)We already found a set in this case you need carry previous result to next one.
  2. (inside else) After subtracting result become false, but previous result is true. so you need to carry that result.

why? [3, 34, 4, 12, 5, 2]

Do not forget the part that DP has Optimal Substructure properties. For, finding sum is 9 we have to find all the sum before it, means 1 to 8. That is exactly you are doing by declaring a W+1 row. So when we calculate sum is 7, for first three values we have a result [3,34,4], we need to carry that result to next level.

So you need to modify previous code, to this

           myArray[i][x] = myArray[i-1][x];//carrying previous result
            if(x>=arr[i] )
            {
                if (myArray[i][x]==1){
                    myArray[i][x]=1; 
                }
                else{
                    myArray[i][x] = myArray[i-1][x-arr[i]];
                }
            }

You also have array indexing issue. Your i and x both start from 1 and you never consider the index 0 which is actually your first player. you need to take arr[i-1] value

so further update will look like this,

        myArray[i][x] = myArray[i-1][x];//carrying previous result
                if(x>=arr[i-1] )
                {
                    if (myArray[i][x]==1){
                        myArray[i][x]=1; 
                    }
                    else{
                        myArray[i][x] = myArray[i-1][x-arr[i-1]];
                    }
                }

So final program will look like this

    public boolean findSolution(int[] scores, int total) {
    int W = total;
    int players = scores.length;

    boolean[][] myArray = new boolean[players + 1][total + 1];

    for (int player = 0; player <= players; player++) {
        myArray[player][0] = true;
    }
    for (int score = 1; score < total; score++) {
        myArray[0][score] = false;
    }
    for (int player = 1; player <= players; player++) {
        for (int score = 1; score <= total; score++) {
            myArray[player][score] = myArray[player - 1][score];
            if (score >= scores[player - 1]) {
                myArray[player][score] = myArray[player - 1][score
                        - scores[player - 1]]
                        || myArray[player][score];
            }
        }
    }
    return myArray[players][W];

}

Now for printing result, look into true values in the matrix. it shouldn't be difficult to find out which values are set and when it was set. print those index to get the result.

minhaz
  • 4,233
  • 3
  • 33
  • 49
  • I know you probably don't have much time but if you can just show how to print. Because i am trying to loop through myArray but i am printing only boolean values instead i want to print the appropriate index values. It would help a ton as it would completely solve this problem . – user1010101 Mar 05 '15 at 18:49
1

Here's the super naive solution that simply generates a power set on your input array and then iterates over each set to see if the sum satisfies the given total. I hacked it together with code already available on StackOverflow.

O(2n) in time and space. Gross.

You can use the idea of a Set to store all indices into your arrays, then generate all permutations of those indices, and then use each set of indices to then go back into your array and get the values.

Input

  • Target: 10
  • Values: [3, 5, 5, 7]

Code:

import java.util.*;
import java.lang.*;
import java.io.*;

class SubsetSum
{
    public static <T> Set<Set<T>> powerSet(Set<T> originalSet)
    {
        Set<Set<T>> sets = new HashSet<Set<T>>();
        if (originalSet.isEmpty()) 
        {
            sets.add(new HashSet<T>());
            return sets;
        }
        List<T> list = new ArrayList<T>(originalSet);
        T head = list.get(0);
        Set<T> rest = new HashSet<T>(list.subList(1, list.size())); 
        for (Set<T> set : powerSet(rest))
        {
            Set<T> newSet = new HashSet<T>();
            newSet.add(head);
            newSet.addAll(set);
            sets.add(newSet);
            sets.add(set);
        }       
        return sets;
    }

    public static void main(String[] args)
    {
        Set<Integer> mySet = new HashSet<Integer>();
        int[] arr={3, 5, 5, 7};
        int target = 10;
        int numVals = 4;
        for(int i=0;i<numVals;++i)
        {
            mySet.add(i);
        }
        System.out.println("Solutions: ");
        for (Set<Integer> s : powerSet(mySet)) 
        {
            int sum = 0;
            for (Integer e : s)
            {
                sum += arr[e];
            }
            if (sum == target)
            {
                String soln = "[ ";
                for (Integer e : s)
                {
                    soln += arr[e];
                    soln += " ";
                }
                soln += "]";

                System.out.println(soln);
            }
        }
    }
}

Output

Solutions:
[ 5 5 ]
[ 3 7 ]

Live Demo

Once you understand this, perhaps you are ready to begin branch and bound or approximation approaches.

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Thank you for the live demo and solution i will test it out right now thanks much – user1010101 Mar 05 '15 at 14:36
  • i have accepted your answer. thanks once again i would also appreciate if you can help me print the indexes in the logic posted by minaz since it makes most sense to me. i am sorry for trouble caused to you. i been on this problem for past 3-4 days and really want to finish it. – user1010101 Mar 05 '15 at 20:57
  • @user2733436: The short answer is your logic was wrong, and so is minhaz's. It cannot solve for all cases, so why bother? – AndyG Mar 05 '15 at 21:38
  • do u mean it will only solve if integer is relatively small ? – user1010101 Mar 05 '15 at 21:39
  • @user2733436: I mean that the fixes he made are incomplete, but it doesn't matter because the approach is inherently flawed. The algorithm is O(n^2) but it is not possible to evaluate all necessary subsets in such time. Even if max sum is 10. Also, it will break really badly for negative scores, which may or may not happen in your case. – AndyG Mar 05 '15 at 22:14
0

I might try a few things.

First, you're passing in an array with 4 values but later you say there are only three players. I think that's causing some of your difficulties.

For the fastest way I can think of to program this, I might try this.

public static void getSolution(int[] array, int desiredTotal) {
    for (int firstIndex = 0; firstIndex < array.size - 1; ++firstIndex) {
        getSolutionWith(array, desiredTotal, firstIndex);
    }
}

public static void getSolutionWith(int[] array, int desiredTotal, int firstIndex) {
    int lookFor = desiredTotal - array[firstIndex];
    for (int secondIndex = firstIndex + 1; secondIndex < array.size; ++secondIndex) {
        if (array[secondIndex] == lookFor) {
            System.out.printf("%d %d\n", firstIndex + 1, secondIndex + 1);
        }
    }
}

I haven't tested this code, so it might not be perfect. Basically you start at the 0 position (person 1) and you then look at everyone else to see if the first person's value + second person's value equals your total. If so, you print them.

Joseph Larson
  • 8,530
  • 1
  • 19
  • 36
  • This only works if the solution can be discovered with a subset of size 2. Following OP's comments, subset of any size is possible. (So, it's the actual subset sum problem) – AndyG Mar 05 '15 at 00:25
  • I just updated my question, subset can be of any size and also i do not need to print all possible combinations that equal the score i can print any combination that equals the score. Also maximum total score can ever be is 1000 nothing more. – user1010101 Mar 05 '15 at 00:56
  • Then make the lower method recursive, passing in the sum so far. – Joseph Larson Mar 05 '15 at 02:25