LeetCode #279 Medium

Perfect Squares

Find the fewest perfect squares that sum to n.

dynamic-programmingmathbreadth-first-search
Open on LeetCode ↗
02

Intuition

💡

The tempting shortcut is greedy: grab the largest perfect square below n and recurse on the remainder. It fails on 12 -- greedy takes 9, then is stuck adding 1+1+1 for a total of four squares, but 4+4+4 uses only three. The largest square is not always part of an optimal decomposition. The fix is to treat this as unbounded knapsack: for every amount from 1 to n, try EVERY perfect square not exceeding it and keep the option that leaves the cheapest remainder. The invariant: dp[i] is the fewest squares that sum to i, and it is only correct once every candidate square has been considered at every amount, not just the biggest one.

03

Approach

1

Precompute candidates

Build the list of perfect squares (1, 4, 9, ...) up to n; these are the only pieces the sum can be built from.

2

Try every square at every amount

dp[0] = 0. For each amount i from 1 to n, scan every square s <= i and take dp[i] = min over all such s of (1 + dp[i - s]). This is unbounded knapsack -- each square can be reused any number of times, and nothing is skipped just because it is not the largest.

3

Final answer

dp[n] holds the minimum count. Because every amount considers all candidate squares, the greedy failure mode (locking in the biggest square too early) never occurs.

04

Solution & live demo

python
1class Solution:
2 def numSquares(self, n: int) -> int:
3 squares = [k * k for k in range(1, int(n ** 0.5) + 1)]
4 dp = [0] + [float('inf')] * n
5 for i in range(1, n + 1):
6 for s in squares:
7 if s > i:
8 break
9 dp[i] = min(dp[i], dp[i - s] + 1)
10 return dp[n]
05

Edge cases

n is itself a perfect square

dp[n] = 1, found directly when the square equal to n is tried.

n = 12 (the greedy counterexample)

dp[12] correctly resolves to 3 (4+4+4), not the greedy 4 (9+1+1+1).

n = 1

dp[1] = 1 trivially, using the square 1 itself.

Large n with no small squares fitting the leftover well

The DP still checks every square size at every amount, so it cannot get trapped by an early bad choice the way greedy does.

06

Complexity

Time
O(n * sqrt(n))
Space
O(n)
For each of n amounts, try up to sqrt(n) candidate squares.