This problem is a variant of the Change-making problem with squares of natural numbers being the available coins denominations—and here is a simple dynamic programming approach.
public static int solution(int number)
{
int[] termsCount = new int[number + 1];
int infinity = number + 1; // bigger than any expected result
termsCount[0] = 0; // done explicitly
for(int i = 1; i <= number; i ++)
termsCount[i] = infinity;
int term;
for(int i = 1; (term = i*i) <= number; i ++)
{
for(int value = term; value <= number; value ++)
{
int prevCount = termsCount[value - term];
if(prevCount != infinity)
{
int newCount = prevCount + 1;
if(newCount < termsCount[value])
termsCount[value] = newCount;
}
}
}
return termsCount[number];
}
To solve the problem for some given sum N (the required 'change', represented by a number variable in the code above) and given term/addends values ('coins' denominations) we may create an array of termCounts, which traces how many terms/coins is enough for each sum from 0 to the required N. We will try to add available terms/coins to partial solutions and choose smaller counts (better partial results) when possible.
So we need to initialize the first element of the array with 0 (zero terms is enough to reach a zero sum) and all remaining elements with infinity (so that the first partial solution will be smaller and will be put into the termCounts array).
A minimum possible term/coin value is 1, so the maximum possible solution is N, hence N+1 will do as infinity.
Once the counters are ready we may start adding terms. They have to be squares of natural numbers, so we can enumerate them with term=i*i iterating from i=1. Iteration stops when term exceeds the target N, because a term/coin can't be greater than an intended sum/change (we can't get a sum of 7 with a term 9 in it, can we?).
With each term value we try to use it for each partial result, stored in termCounts. Starting from value equal term (we can't get sum value less than a term) we check a number of terms (a partial solution) for value-term. If it appears non-infinite, which means we have some solution there, we try to extend it by adding our current term: we add 1 to the count and check if the new term/coin count for current value is smaller than what we already have at termCounts[value]. If so, we got a better solution for value, then we store a new count in termCounts.
Note: A no-solution indicator infinite values do not stay long in the solution we consider here. They are overwritten as soon as in the first pass thanks to the first term/coin tested being 1, which allows us to obtain every required sum. Without the 1, however, some sums might remain unreachable with corresponding termCounts elements being infinite. For example, if all the terms/coins available were even, all the odd sums would remain unreachable.
Infinite values also stay temporarily if we start processing terms/coins from bigger values. For example, if we started with term==25 it would fill every 25-th term, and gaps between them would get filled later by lower values/denominations.
This gets iterated across the termsCount array, so we know every solution that can be improved with the new term value gets improved.
Finally every termCounts[value] contains the minimum number of terms from the processed set of perfect squares which make a sum equal value. In particular, the value termsCount[number] returned from the routine is the required answer for the input value number.
Supplement
The first condition in the inner loop is actually not necessary. If the 'previous solution' does not exist yet (that is, prevCount == infinity), then the 'current solution' will appear non-existent, too: newCount = prevCount+1 will be greater than infinity, so it will certainly not satisfy the inner condition, regardless whether the current solution exists or not (is `infinite').
So the inner loop can be safely reduced to:
for(int value = term; value <= number; value ++)
{
int newCount = termsCount[value - term] + 1;
termsCount[value] = Math.min( termsCount[value], newCount);
}
18it returns the solution3(16 + 1 + 1) while the correct solution is2(9 + 9). \$\endgroup\$