Actually, I think you do want lexicographic order, but descending rather than ascending. In addition:
- It's not clear to me from your description that A, B, ... D play any role in your answer (except possibly as the container for the values).
- I think your question example is simply "For each integer at least 5, up to the maximum possible total of two values, how many distinct pairs from the set {3, 3, 2, 1} have sums of that integer?"
- The interesting part is the early bailout, once no possible solution can be reached (remaining achievable sums are too small).
I'll post sample code later.
Here's the sample code I promised, with a few remarks following:
public class Combos {
/* permanent state for instance */
private int values[];
private int length;
/* transient state during single "count" computation */
private int n;
private int limit;
private Tally<Integer> tally;
private int best[][]; // used for early-bail-out
private void initializeForCount(int n, int limit) {
this.n = n;
this.limit = limit;
best = new int[n+1][length+1];
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= length - i; ++j) {
best[i][j] = values[j] + best[i-1][j+1];
}
}
}
private void countAt(int left, int start, int sum) {
if (left == 0) {
tally.inc(sum);
} else {
for (
int i = start;
i <= length - left
&& limit <= sum + best[left][i]; // bail-out-check
++i
) {
countAt(left - 1, i + 1, sum + values[i]);
}
}
}
public Tally<Integer> count(int n, int limit) {
tally = new Tally<Integer>();
if (n <= length) {
initializeForCount(n, limit);
countAt(n, 0, 0);
}
return tally;
}
public Combos(int[] values) {
this.values = values;
this.length = values.length;
}
}
Preface remarks:
This uses a little helper class called Tally, that just isolates the tabulation (including initialization for never-before-seen keys). I'll put it at the end.
To keep this concise, I've taken some shortcuts that aren't good practice for "real" code:
- This doesn't check for a null value array, etc.
- I assume that the value array is already sorted into descending order, required for the early-bail-out technique. (Good production code would include the sorting.)
- I put transient data into instance variables instead of passing them as arguments among the private methods that support
count
. That makes this class non-thread-safe.
Explanation:
An instance of Combos
is created with the (descending ordered) array of integers to combine. The value
array is set up once per instance, but multiple calls to count
can be made with varying population sizes and limits.
The count
method triggers a (mostly) standard recursive traversal of unique combinations of n
integers from values
. The limit
argument gives the lower bound on sums of interest.
The countAt
method examines combinations of integers from values
. The left
argument is how many integers remain to make up n
integers in a sum, start
is the position in values
from which to search, and sum
is the partial sum.
The early-bail-out mechanism is based on computing best
, a two-dimensional array that specifies the "best" sum reachable from a given state. The value in best[n][p]
is the largest sum of n
values beginning in position p
of the original values
.
The recursion of countAt
bottoms out when the correct population has been accumulated; this adds the current sum
(of n
values) to the tally
. If countAt
has not bottomed out, it sweeps the values
from the start
-ing position to increase the current partial sum
, as long as:
- enough positions remain in
values
to achieve the specified population, and
- the
best
(largest) subtotal remaining is big enough to make the limit
.
A sample run with your question's data:
int[] values = {3, 3, 2, 1};
Combos mine = new Combos(values);
Tally<Integer> tally = mine.count(2, 5);
for (int i = 5; i < 9; ++i) {
int n = tally.get(i);
if (0 < n) {
System.out.println("found " + tally.get(i) + " sums of " + i);
}
}
produces the results you specified:
found 2 sums of 5
found 1 sums of 6
Here's the Tally code:
public static class Tally<T> {
private Map<T,Integer> tally = new HashMap<T,Integer>();
public Tally() {/* nothing */}
public void inc(T key) {
Integer value = tally.get(key);
if (value == null) {
value = Integer.valueOf(0);
}
tally.put(key, (value + 1));
}
public int get(T key) {
Integer result = tally.get(key);
return result == null ? 0 : result;
}
public Collection<T> keys() {
return tally.keySet();
}
}