Notice that, for each of the sequences listed above, if the non-underlined elements are removed, what's left is the subsequence <dog, cat, pig>.
On the other hand, <dog, cat, pig> is a subsequence of neither <horse, dog, emu, pig, cow> nor <horse, cat, dog, emu, pig, cow>, the former because cat is missing and the latter because, even though all three of dog, cat, and pig are present, they do not appear in the correct order.
For the purposes of this assignment, we are interested in subsequences of the sequence <0, 1, 2, ..., N−1>, for some N≥0. All such sequences are thus of the form <i1, i2, ..., ik>, where 0 ≤ i1 < i2 < ..., < ik < N.
The values in the sequences we are interested in refer to locations in an array.
Given an array A[0..N) of positive integers and a "target" value m, does there exist a subsequence <i1, i2, ..., ik> of <0,1,...,N−1> such thatA[i1] + A[i2] + ... + A[ik] = m
A problem such as this, in which the "answer" to each instance is either "YES" or "NO", is called a decision problem.
Consider the instance of the problem in which m = 8 and A is
0 1 2 3 4 5 6 7 +----+----+----+----+----+----+----+----+ A | 5 | 13 | 2 | 4 | 1 | 3 | 5 | 6 | +----+----+----+----+----+----+----+----+ |
The answer is "YES". Indeed, there are six distinct ways of choosing locations in A such that the sum of the elements at those locations is 8:
A[0] + A[2] + A[4] | = | 5 + 2 + 1 | = | 8 |
A[0] + A[5] | = | 5 + 3 | = | 8 |
A[3] + A[4] + A[5] | = | 4 + 1 + 3 | = | 8 |
A[2] + A[4] + A[6] | = | 2 + 1 + 5 | = | 8 |
A[5] + A[6] | = | 3 + 5 | = | 8 |
A[2] + A[7] | = | 2 + 6 | = | 8 |
This observation suggests a different version of the problem:
Given an array A[0..N) of positive integers and a "target" value m, how many distinct subsequences <i1, i2, ..., ik> of <0,1,...,N−1> are such thatA[i1] + A[i2] + ... + A[ik] = m
This kind of problem, in which the answer to each instance is a natural number describing the size of a set of "solutions", is called a counting problem.
With respect to the problem instance described above (in which m = 8 and A[] is the array in the picture), the answer is 6.
Another variation of this problem calls for a description of each solution to be produced (e.g., printed). This kind of problem is called an enumeration problem.
With respect to the problem instance described above (in which m = 8 and A[] is the array in the picture), the answer might be something like this:
Given array A and target m, determine whether or not there is a subsequence <i1, i2, ..., ik> of <0,1,...,N−1> (where N = A.length) satisfying A[i1] + A[i2] + ... + A[ik] = m.
As is often the case, developing a recursive solution is aided by generalizing the problem in a particular way. Here, we generalize the problem to
Given array A, integer n (0≤n≤A.length), and target m, determine whether or not there is a subsequence <i1, i2, ..., ik> of <0,1,...,n−1> satisfying A[i1] + A[i2] + ... + A[ik] = m.
That is, we generalize the problem so that each of its instances is described by a triple of parameters (A,n,m) rather than by only a pair (A,m). Indeed, the problem instance (A,m) (of the original problem) is equivalent to the problem instance (A,N,m) (where N = A.length) of the generalized problem.
Using the generalized form of the problem, we can easily describe a recursive solution to the instance (A,n,m):
The first three cases (all base cases) should be clear. As for the fourth (recursive) case, consider once again the array
0 1 2 3 4 5 6 7 +----+----+----+----+----+----+----+----+ A | 5 | 13 | 2 | 4 | 1 | 3 | 5 | 6 | +----+----+----+----+----+----+----+----+ |
and consider the problem instance (A,8,10), which asks whether there is any way to get 10 by choosing zero or more of the locations in A[0..8) and summing the values therein.
None of the base cases apply, so according to the recursive case, the answer is YES iff at least one among the problem instances (A,7,10) or (A,7,4) has a YES answer. (Why 4? Because if there are elements in A[0..7) that sum to 4, then those elements, plus A[7] (which is 6) sum to 10, the target.)
To say it in yet another way, it is possible to form a sum of 10 among the elements of A[0..8) iff either
The solutions in category (1) are those in which location 7 is not included. Those in category (2) are those that do include location 7.
As for the counting version of the problem, a recursive solution flows from the solution to the decision problem described above. The relevant observations that make the adjustment straightforward are these
As for the enumeration version of the problem, an algorithm following the same logic as described above will work. However, it is a little bit more tricky, because each time the existence of a "new" solution is verified, it is not enough to simply say return 1, as would be the case inside a method that solves the counting version of the problem. Instead, a method that enumerates solutions must have access to a "partial solution" to which it can add a location as it descends the recursion tree. When it reaches the point where it has verified the existence of a "new" solution, it can print that solution.
The mechanism by which an enumeration method can access such a partial solution is an accumulating parameter.
Provided are the following files:
$ java SubSeqSumApp input5.txt Array is [5, 9, 13, 6, 21, 9, 18, 25, 19, 15] Enter target value (-1 to quit): 17 exists(17) yields false numberOf(17) yields 0 enumerate(17) yields: Enter target value (-1 to quit): 46 exists(46) yields true numberOf(46) yields 11 enumerate(46) yields: [1 [2 [3 [6 []]]]]; Sum = 46 [2 [3 [5 [6 []]]]]; Sum = 46 [4 [7 []]]; Sum = 46 [0 [1 [2 [8 []]]]]; Sum = 46 [3 [4 [8 []]]]; Sum = 46 [0 [2 [5 [8 []]]]]; Sum = 46 [1 [6 [8 []]]]; Sum = 46 [5 [6 [8 []]]]; Sum = 46 [1 [2 [5 [9 []]]]]; Sum = 46 [2 [6 [9 []]]]; Sum = 46 [3 [7 [9 []]]]; Sum = 46 Enter target value (-1 to quit): 25 exists(25) yields true numberOf(25) yields 2 enumerate(25) yields: [7 []]; Sum = 25 [3 [8 []]]; Sum = 25 Enter target value (-1 to quit): -2 [5, 9, 13, 6, 21, 9, 18, 25, 19, 15] Enter target value (-1 to quit): 58 exists(58) yields true numberOf(58) yields 19 enumerate(58) yields: [1 [2 [3 [4 [5 []]]]]]; Sum = 58 [2 [3 [4 [6 []]]]]; Sum = 58 [0 [1 [2 [3 [7 []]]]]]; Sum = 58 [0 [2 [3 [5 [7 []]]]]]; Sum = 58 [1 [3 [6 [7 []]]]]; Sum = 58 [3 [5 [6 [7 []]]]]; Sum = 58 [0 [2 [4 [8 []]]]]; Sum = 58 [1 [4 [5 [8 []]]]]; Sum = 58 [4 [6 [8 []]]]; Sum = 58 [0 [1 [7 [8 []]]]]; Sum = 58 [0 [5 [7 [8 []]]]]; Sum = 58 [1 [2 [4 [9 []]]]]; Sum = 58 [2 [4 [5 [9 []]]]]; Sum = 58 [0 [2 [7 [9 []]]]]; Sum = 58 [1 [5 [7 [9 []]]]]; Sum = 58 [6 [7 [9 []]]]; Sum = 58 [0 [2 [3 [8 [9 []]]]]]; Sum = 58 [1 [3 [5 [8 [9 []]]]]]; Sum = 58 [3 [6 [8 [9 []]]]]; Sum = 58 Enter target value (-1 to quit): -1 Goodbye. |