13

The problem is to find the largest set S of positive integers such that the sum of the squares of the elements of S is equal to a given number n.

For example:

4 = 2²
20 = 4² + 2²
38 = 5² + 3² + 2²
300 = 11² + 8² + 7² + 6² + 4² + 3² + 2² + 1².

I have an algorithm that runs in time O(2^(sqrt n) * n), but it's too slow (every subset of squares).

Eryk Pawilan
  • 143
  • 7
  • So the problem is that you're after a faster algorithm? – Werner Oct 06 '14 at 18:59
  • Unfortunately my solution is too slow. – Eryk Pawilan Oct 06 '14 at 19:03
  • It just wasn't clear from your question, since you don't mention it at all. – Werner Oct 06 '14 at 19:17
  • 3
    The example you gave is wrong. 300 can be written as 11² + 8² + 7² + 6² + 4² + 3² + 2² + 1². – Juan Lopes Oct 06 '14 at 19:44
  • OP, are you familiar with *dynamic programming*? You can read about it in any good algorithms book. – Colonel Panic Oct 07 '14 at 10:07
  • By the way, not all numbers can be written as a sum of distinct squares. For example 6 or 2. – Colonel Panic Oct 09 '14 at 13:23
  • If anyone is interested, [strongly connected topic](http://math.stackexchange.com/q/1006621/186012) on mathSE. I can't prove that problems are equal, so I didn't put it (using this observation solution) as an answer. Maybe I'll do it in future. If my assumptions are true, it is possible to solve in `O(N*lg(n))`, where N is number of squares in sum. – Tacet Dec 14 '14 at 02:01

3 Answers3

7

There's an O(n^1.5)-time algorithm based on the canonical dynamic program for subset sum. Here's the recurrence:

C(m, k) is the size of the largest subset of 1..k whose squares sum to m
C(m, k), m < 0 = -infinity (infeasible)
C(0, k) = 0
C(m, 0), m > 0 = -infinity (infeasible)
C(m, k), m > 0, k > 0 = max(C(m, k-1), C(m - k^2, k-1) + 1)

Compute C(m, k) for all m in 0..n and all k in 0..floor(n^0.5). Return C(n, floor(n^0.5)) for the objective value. To recover the set, trace back the argmaxes.

David Eisenstat
  • 52,844
  • 7
  • 50
  • 103
4

I was just wondering if this problem reduce to NP? Looks like you have list of integers (squares) less than n (could be generated in O(sqrt(n))) and you're looking for subset sum of size from 1 to sqrt(n) (check all posibilities). If so it should be solvable with knapsack dynamic programming algorithm (but this is pretty naive algorithm and I think it could be improved) in O(n^2) - sqrt(n) of problems to check times sqrt(n) knapsack items count times n knapsack weight.

EDIT: I think with smart backtracking after filling dynamic programming array you could do it in O(n*sqrt(n)).

fex
  • 3,396
  • 4
  • 26
  • 44
4

You can use the recurrence:

T(0, m) = 0
T(n, m) = -Infinity (if n<0 or m<0)
T(n, m) = max(T(n-m*m, m-1)+1, T(n, m-1))

Or, in Python code:

from functools import lru_cache

@lru_cache(100000)
def T(n, m):
    if n<0 or m<0: return (-1000000, 0)
    if n==0: return (0, 0)
    return max((T(n-m*m, m-1)[0]+1, m), T(n, m-1)) 

def squares(n):
    s = int(n**0.5)
    while n>0 and s>0:
        _, factor = T(n, s)
        yield factor**2
        n -= factor**2
        s = factor-1

for x in (4, 20, 38, 300):
    result = list(squares(x))
    print(sum(result), '= sum', result) 

The example you gave (300), can be written with 8 factors as:

300 = 11² + 8² + 7² + 6² + 4² + 3² + 2² + 1²

Other results:

4 = sum [4]
20 = sum [16, 4]
38 = sum [25, 9, 4]
300 = sum [121, 64, 49, 36, 16, 9, 4, 1]
Juan Lopes
  • 9,563
  • 2
  • 23
  • 41