0

Trying to solve this problem with recursion but for input 7168 getting wrong answer.

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

def recursive(self, n, result, dp):
    if n in dp:
        return dp[n]
    #very large number
    large_no = 1 << 31
    if n < 1:
        return 0      
    #checking if n is a square or not?  
    r = n**0.5
    if int(r)*int(r) == n:
        return result + 1 
    #iterate from square root till 1 checking all numbers
    r = int(r)
    while r >= 1:
        large_no = min(large_no, self.recursive(n - int(r)*int(r), result + 1, dp))
        r -= 1
    #memoize the result
    dp[n] = large_no
    return large_no

I am calling above function as this: self.recursive(7168, 0, {})

Answer should be 4 but I am getting 5. Please don't suggest alternative ways to solve this problem as I have tried them already and it works. I am here to just know the problem with this logic.

Community
  • 1
  • 1
newbie_old
  • 500
  • 5
  • 19
  • 1
    Add a print to the top of the method: `print(n, result, dp)`. Run it for a small number (eg 12) and look and the values you are memoizing. They seem wrong to me: eg `{2: 4, 3: 4, 5: 6, ...}`. Is that what you intend to do? Normally, you want to memoize the correct answer: eg, `{2:2, 3:3, 4:1, etc}`. Maybe I don't understand your algorithm. – FMc Sep 03 '17 at 08:16
  • So what's the optimal four-term decomposition of 7168 (that your code isn't finding)? – NPE Sep 03 '17 at 08:29
  • @NPE (80,16,16,16) – n. m. could be an AI Sep 03 '17 at 08:57

2 Answers2

1

I think the problem is that you're passing result down into your recursion but do not take it into account in memoizing.

recursive(X, Y, dp) and recursive(X, Z, dp) both return dp[X] if X in dp but return dp[X] + y and dp[X] + z, respectively, if dp[X] is not yet memoized (where y = R - Y and z = R - Z, with R the value of result when dp[X] got memoized).

I would get rid of result altogether:

def recursive(self, n, dp):
    if n in dp:
        return dp[n]
    #very large number
    large_no = 1 << 31
    if n < 1:
        return 0      
    #checking if n is a square or not?  
    r = n**0.5
    if int(r)*int(r) == n:
        return 1 
    #iterate from square root till 1 checking all numbers
    r = int(r)
    while r >= 1:
        large_no = min(large_no, self.recursive(n - int(r)*int(r), dp))
        r -= 1
    #memoize the result
    dp[n] = large_no + 1
    return large_no + 1
Vincent van der Weele
  • 12,927
  • 1
  • 33
  • 61
1

First, you have a typo: m should be large_no.

But you're using dp incorrectly: you should be caching the smallest way to write i as the sum of squares, but you're actually caching the result of whatever path you happen to get there.

That means you may cache an accidentally larger value than necessary, and your algorithm is wrong. Although the algorithm is wrong, 7168 is the first value for which it produces the wrong result.

Drop the result argument, change return result+1 to return 1 and your recursive call to:

large_no = min(large_no, 1+self.recursive(n - int(r)*int(r), dp))

A cleaned-up, working version of your code:

def recursive(n, dp):
    if n in dp:
        return dp[n]
    if n == 0: return 0
    best = n
    for r in xrange(int(n**0.5), 0, -1):
        best = min(best, 1 + recursive(n - r*r, dp))
    dp[n] = best
    return dp[n]
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • Yup the recurrence relation is same: dp[i] = min(1 + dp[i-j*j]) if dp[i] stands for minimum squares to reach i where j starts from 1 to square root of i. – newbie_old Sep 03 '17 at 08:43