There is one technique, of transforming a problem to a more comfortable one.
Here one could do it a couple of times:
First the original problem sum = term[0] ± term[1] ± ...
.
static boolean additive(int sum, int... terms) {
if (terms.length == 0) {
return sum == 0;
}
if (terms.length == 1) {
return sum == terms[0];
}
int sum2 = sum - terms[0];
int[] terms2 = IntStream.of(terms).skip(1).map(Math::abs).sorted().toArray();
// Also an occasion to use Arrays.copyOfRange(terms, 1, terms.length);
return additive2(sum2, terms2);
}
You can subtract the first term from the sum, and then have the possibly more regular problem sum == ∑i ±term[i]
. All terms positive, and sorted.
/**
* @param sum to be formed by the sum of every term * ± 1.
* @param terms, all absolute, and sorted.
*/
static boolean additive2(int sum, int... terms) {
int termsSum = IntStream.of(terms).sum();
if (sum > termsSum) {
return false;
}
int tooMuch = termsSum - sum;
// Split the terms in added and subtracted ones.
// The sum of the subtracted ones * 2 == tooMuch.
if (tooMuch % 2 != 0) {
return false;
}
if (tooMuch == 0) { // All positive terms.
return true;
}
// Find subtracted terms:
int subtractedSum = tooMuch / 2;
return findSubtracted(subtractedSum, terms.length - 1, terms);
}
Then math properties appear: the difference between the sum of absolute terms and the requested sum must be twice the sum of the negative/subtracted terms.
An odd difference means failure. Nice.
Now one only needs to recursively find the subtracted terms, starting with the largest terms. O(2n) but with some cuts: if the term is too large, in the recursion sum < 0, and done: sum is decreased as fast as possible.
static boolean findSubtracted(int sum, int i, int[] terms) {
if (sum <= 0) {
return sum == 0;
}
if (i < 0) {
return false;
}
return findSubtracted(sum - terms[i], i - 1, terms)
|| findSubtracted(sum, i - 1, terms);
}
public static void main(String[] args) throws XMLStreamException {
System.out.println(additive(12, 1, 2, 3, 4, 5, 7)); // true, because 1 + 2 + 3 + 4 - 5 + 7 = 12
System.out.println(additive(7, 5, 3)); // false, because neither 5 + 3 != 7 or 5 - 3 != 7
}
Take
- Problem: 1 + 2 + 3 + 4 - 5 + 7 = 12
- 1 + 2 + 3 + 4 + 5 + 7 = 22; tooMuch = 10, subtractedSum = 5
- -7? - false
- -5? - true
This might not be the best solution, but it exposes some math intelligence.
For instance 1 + 2 + 3 + 4 - 5 + 7 = 13
will fail fast.
Dynamic programming, operations research and such are indeed worthwile in real life problems, especially if approximations / near solutions count too. Traffic optimisation and such.
A remark on iterating upto 2n with bit tests: nice and I did it myself on occasion. However here my code accomplishes the same with 2 recursive calls in the function. Which certainly is less effective, but more readable. Do this (micro-)optimisation last, as it hampers mental flexibility on the mathematical properties themselves. I think.