Problem Definition
Given target 't', and a collection of positive integers w1, w2, w3, ..., wn, determine whether there is some selection among the weights so that the total is exactly 't'.
For ex, if t = 10, and the weights are 7, 5, 4, 4, and 1, then the answer is - yes there is selection 5, 4, 1 whose some is '10'
Brute Force
One obvious solution is going through all the subsets of given set of weights, and see if there is any combination that sums up to given total. As we all know set of 'n' elements will have up-to 2^n subsets - which are nothing but no. of combinations. So, the algorithm will be exponential.
Basic gist of the below algorithm is that either item contributes to get to given target 't' or not. So, initially it assumes that the item contributes to get to the given target, and see if rest of the components can contribute to remaining weight. If it is not possible then it checks for the other possibility that the item doesn't contribute to get to the given target, and see if the remaining elements can form the target.
Code Snippet
- private bool KnapsackSimplifiedProblemRecursive(int[] weights, int sum, int currentSum, int index, List<int> itemsInTheKnapsack)
- {
- if (currentSum == sum)
- {
- return true;
- }
- if (currentSum > sum)
- {
- return false;
- }
- if (index >= weights.Length)
- {
- return false;
- }
- itemsInTheKnapsack.Add(weights[index]);
- bool flag = KnapsackSimplifiedProblemRecursive(weights, sum, currentSum: currentSum + weights[index],
- index: index + 1, itemsInTheKnapsack: itemsInTheKnapsack);
- if (!flag)
- {
- itemsInTheKnapsack.Remove(weights[index]);
- flag = KnapsackSimplifiedProblemRecursive(weights, sum, currentSum, index + 1, itemsInTheKnapsack);
- }
- return flag;
- }
Basically above private method will be invoked by the following public method.
Code Snippet
- public bool KnapsackRecursive(int[] weights, int sum, out List<int> knapsack)
- {
- if(sum <= 0)
- {
- throw new ArgumentException("sum should be +ve non zero integer");
- }
- knapsack = new List<int>();
- bool fits = KnapsackSimplifiedProblemRecursive(weights, sum, currentSum: 0, index: 0,
- itemsInTheKnapsack: knapsack);
- return fits;
- }
Time Complexity
Another way to prove that above algorithm is exponential is by considering worst case. Assume that the given target cannot be achieved by the given weights. That means, it essentially go through all combinations which is clearly exponential.Removing tail recursion
Tail recursion means, at the end of the method typically there will be just recursive call. typically these can be removed either with using a stack or goto statements. Below is the version of removing tail recursion using stack:
Code Snippet
- public bool SubsetSum_NoRepetition_KnapsackSimplifiedProblemIterativeUsingStack(int[] weights, int sum, out List<int> knapsack)
- {
- this.Validate(weights, sum);
- var knapsackIndices = new List<int>();
- knapsack = new List<int>();
- Stack<SubsetSum_NoRepetition_KnapsackStackNode> stack = new Stack<SubsetSum_NoRepetition_KnapsackStackNode>();
- stack.Push(new SubsetSum_NoRepetition_KnapsackStackNode() { sumOfWeightsInTheKnapsack = 0, nextItemIndex = 1 });
- stack.Push(new SubsetSum_NoRepetition_KnapsackStackNode() { sumOfWeightsInTheKnapsack = weights[0], nextItemIndex = 1, includesItemAtCurrentIndex = true });
- knapsackIndices.Add(0);
- while (stack.Count > 0)
- {
- var top = stack.Peek();
- if (top.sumOfWeightsInTheKnapsack == sum)
- {
- int count = 0;
- foreach (var index in knapsackIndices)
- {
- var w = weights[index];
- knapsack.Add(w);
- count += w;
- }
- Debug.Assert(count == sum);
- return true;
- }
- //basically either of the below three cases, we dont need to traverse/explore adjuscent
- // nodes further
- if ((top.nextItemIndex == weights.Length) //we reached end, no need to traverse
- || (top.sumOfWeightsInTheKnapsack > sum) // last added node should not be there
- || (top.alreadyExploredAdjuscentItems)) //already visted
- {
- if (top.includesItemAtCurrentIndex)
- {
- Debug.Assert(knapsackIndices.Contains(top.nextItemIndex - 1));
- knapsackIndices.Remove(top.nextItemIndex - 1);
- }
- stack.Pop();
- continue;
- }
- var node1 = new SubsetSum_NoRepetition_KnapsackStackNode();
- node1.sumOfWeightsInTheKnapsack = top.sumOfWeightsInTheKnapsack;
- node1.nextItemIndex = top.nextItemIndex + 1;
- stack.Push(node1);
- var node2 = new SubsetSum_NoRepetition_KnapsackStackNode();
- knapsackIndices.Add(top.nextItemIndex);
- node2.sumOfWeightsInTheKnapsack = top.sumOfWeightsInTheKnapsack + weights[top.nextItemIndex];
- node2.nextItemIndex = top.nextItemIndex + 1;
- node2.includesItemAtCurrentIndex = true;
- stack.Push(node2);
- top.alreadyExploredAdjuscentItems = true;
- }
- return false;
- }
- class SubsetSum_NoRepetition_KnapsackStackNode
- {
- public int sumOfWeightsInTheKnapsack;
- public int nextItemIndex;
- public bool alreadyExploredAdjuscentItems;
- public bool includesItemAtCurrentIndex;
- }
Reference(s)
"DataStructures and Algorithms" by Aho, Hopcroft, and Ullman http://www.amazon.com/Data-Structures-Algorithms-Alfred-Aho/dp/0201000237)
http://www.geeksforgeeks.org/dynamic-programming-subset-sum-problem/
http://www.geeksforgeeks.org/dynamic-programming-subset-sum-problem/