LeetCode #279 Medium

Perfect Squares

Perfect Squares is LeetCode 279 (Medium). You are given a positive integer n. Return the smallest number of perfect squares that add up to exactly n.

  • A perfect square is the square of a whole number: 1, 4, 9, 16, 25 and so on.
  • The same square may be used as many times as you like.
  • Only the count matters; you do not return which squares were used.

n is at most 10⁴. There are at most 100 squares below that bound, so trying every square for every value up to n is about a million steps, which is the budget the intended solution fits in.

Constraints
  • 1 <= n <= 10⁴
dynamic-programmingmathbreadth-first-search
Open on LeetCode ↗
02

Intuition

Taking the largest square that fits looks right, but it can leave a remainder that is expensive to finish, so no single choice can be trusted. Look at the problem from the other end instead.

Whatever the best sum for n is, it has some last square s. Remove it and what is left, n − s, must itself use as few squares as possible, or swapping in a cheaper sum would beat the optimum. So the answer for n is 1 plus the best answer for n − s, minimised over every square s ≤ n. Every n − s is smaller than n, so solving 0, 1, 2, … in order has each answer ready before it is needed.

How to spot this pattern

"Fewest pieces that add up to a target, any piece reusable" is unbounded knapsack, the same shape as Coin Change with the coins fixed to 1, 4, 9, 16 and so on. When a greedy pick looks tempting but the piece values do not divide each other, expect a counterexample and reach for the DP.

03

Approach

Try it first

Before reading on, work out by hand the fewest squares for 1 through 13 and see where greedy first gives the wrong count. Then write the recurrence that turns your table into code. Aim for O(n√n).

1

Set up the table

Make dp of size n + 1, where dp[i] is the fewest squares that sum to i. Set dp[0] = 0, because the empty sum needs no squares, and every other entry to n, a safe ceiling since i ones always work.

2

Try every square as the last one

For each i from 1 to n, loop over the squares s ≤ i and keep dp[i] = min(dp[i], dp[i − s] + 1). The + 1 pays for s itself, and dp[i − s] is already the cheapest way to make the rest.

3

Fill the table in increasing order

Going from i = 1 upward guarantees every dp[i − s] is final before it is read. List the squares once up front and stop the inner loop at the first one larger than i; that keeps a perfect squares Python solution near 10⁶ simple steps for n = 10⁴.

4

Return dp[n]

After the loop, dp[n] is the answer. It can never stay at the ceiling: 1 is a square, so every value is at most one more than the value just below it, and the answer is never -1.

04

Perfect Squares solution in Python | C++ | Java

▶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] + [n] * n
▶5 for i in range(1, n + 1):
▶6 for sq in squares:
▶7 if sq > i:
▶8 break
▶9 dp[i] = min(dp[i], dp[i - sq] + 1)
▶10 return dp[n]
dp00123456789101112dp[0] = 0, fill left to right
n12
squares1, 4, 9every square ≤ n
dp[0]0the empty sum
State. Tile i will hold the fewest squares that sum to i. Only 0 is known up front: it needs no squares at all. Every other value is built from smaller ones, so the row fills strictly left to right.
dp001123456789101112last squareleftoverits dptotal100+ 11mini = 1 needs 1 square
i1
tried1 squareevery square ≤ 1
dp[1]1last square 1
Only 1 fits under 1, so the last square must be 1 and the rest is 0, which already costs 0. That gives 1.
dp0011223456789101112last squareleftoverits dptotal111+ 12mini = 2 needs 2 squares
i2
tried1 squareevery square ≤ 2
dp[2]2last square 1
Only 1 fits under 2, so the last square must be 1 and the rest is 1, which already costs 1. That gives 2.
dp00112233456789101112last squareleftoverits dptotal122+ 13mini = 3 needs 3 squares
i3
tried1 squareevery square ≤ 3
dp[3]3last square 1
Only 1 fits under 3, so the last square must be 1 and the rest is 2, which already costs 2. That gives 3.
dp001122331456789101112last squareleftoverits dptotal133+ 14400+ 11mini = 4 needs 1 square
i4
tried2 squaresevery square ≤ 4
dp[4]1last square 4
4 is a perfect square, so using it leaves 0, which costs nothing: one square in total. No other choice can beat 1.
dp0011223314256789101112last squareleftoverits dptotal141+ 12min411+ 12i = 5 needs 2 squares
i5
tried2 squaresevery square ≤ 5
dp[5]2last square 1
Try each square as the last one. Using 1 leaves 4, which the row already says costs 1, so 2 in total; another square ties, and the first minimum is kept.
dp00112233142536789101112last squareleftoverits dptotal152+ 13min422+ 13i = 6 needs 3 squares
i6
tried2 squaresevery square ≤ 6
dp[6]3last square 1
Try each square as the last one. Using 1 leaves 5, which the row already says costs 2, so 3 in total; another square ties, and the first minimum is kept.
dp001122331425364789101112last squareleftoverits dptotal163+ 14min433+ 14i = 7 needs 4 squares
i7
tried2 squaresevery square ≤ 7
dp[7]4last square 1
Try each square as the last one. Using 1 leaves 6, which the row already says costs 3, so 4 in total; another square ties, and the first minimum is kept.
dp0011223314253647289101112last squareleftoverits dptotal174+ 15441+ 12mini = 8 needs 2 squares
i8
tried2 squaresevery square ≤ 8
dp[8]2last square 4
Try each square as the last one. Using 4 leaves 4, which the row already says costs 1, so 2 in total. Using 1 leaves 7 (cost 4), which is worse.
dp00112233142536472819101112last squareleftoverits dptotal182+ 13452+ 13900+ 11mini = 9 needs 1 square
i9
tried3 squaresevery square ≤ 9
dp[9]1last square 9
9 is a perfect square, so using it leaves 0, which costs nothing: one square in total. No other choice can beat 1.
dp001122331425364728192101112last squareleftoverits dptotal191+ 12min463+ 14911+ 12i = 10 needs 2 squares
i10
tried3 squaresevery square ≤ 10
dp[10]2last square 1
Try each square as the last one. Using 1 leaves 9, which the row already says costs 1, so 2 in total; another square ties, and the first minimum is kept.
dp0011223314253647281921031112last squareleftoverits dptotal1102+ 13min474+ 15922+ 13i = 11 needs 3 squares
i11
tried3 squaresevery square ≤ 11
dp[11]3last square 1
Try each square as the last one. Using 1 leaves 10, which the row already says costs 2, so 3 in total; another square ties, and the first minimum is kept.
dp00112233142536472819210311312last squareleftoverits dptotal1113+ 14482+ 13min933+ 14i = 12 needs 3 squares
i12
tried3 squaresevery square ≤ 12
dp[12]3last square 4
Try each square as the last one. Using 4 leaves 8, which the row already says costs 2, so 3 in total. Using 1 leaves 11 (cost 3) and using 9 leaves 3 (cost 3), which is worse. Note greedy's pick, 9, loses here: its leftover 3 is expensive.
dp00112233142536472819210311312dp picks4+4+4→3squaresgreedy9+1+1+1→4squaresreturn 3
dp[12]3fewest squares
greedy4one too many
Return 3. Following each tile's chosen square back from 12 to 0 recovers the sum itself. Greedy grabs 9 first and pays for it: 4 squares instead of 3. That gap is why every square has to be tried.
05

BFS on remainders

Each level subtracts one more square from every remainder in the frontier. The first level where a remainder is itself a square is the fewest squares needed.

▶1class Solution:
▶2 def numSquares(self, n: int) -> int:
▶3 squares = [k * k for k in range(1, int(n**0.5) + 1)]
▶4 frontier, seen, level = [n], {n}, 0
▶5 while frontier:
▶6 level += 1
▶7 nxt = []
▶8 for v in frontier:
▶9 for sq in squares:
▶10 if sq > v:
▶11 break
▶12 if sq == v:
▶13 return level
▶14 if v - sq not in seen:
▶15 seen.add(v - sq)
▶16 nxt.append(v - sq)
▶17 frontier = nxt
▶18 return level
06

Common pitfalls

Taking the largest square first

✗ Wrong
while n:
    s = int(n**0.5) ** 2
    n -= s
    count += 1
✓ Right
for sq in squares:
    if sq > i:
        break
    dp[i] = min(dp[i], dp[i - sq] + 1)

Greedy gives 9 + 1 + 1 + 1 for 12 (four) when 4 + 4 + 4 (three) exists. Squares are not multiples of each other, so the largest choice can leave a remainder that is expensive to finish.

Leaving dp[0] at the upper bound

✗ Wrong
dp = [n] * (n + 1)
✓ Right
dp = [0] + [n] * n

Every exact square i is found through dp[i − i] + 1. If dp[0] is n, then dp[0] + 1 is n + 1, so dp[4] stays at n instead of 1, and every later value built on it is wrong.

Recursing on n without a memo

✗ Wrong
def f(n):
    return 1 + min(f(n - s) for s in squares if s <= n)
✓ Right
@cache
def f(n):
    ...

The same remainders are reached through many different orders of squares, so plain recursion is exponential. The memo, or the bottom-up table, makes each value cost √n once.

07

Complexity

Time
O(n√n)
Space
O(n)
Each of the n values tries at most √n squares. The table holds n + 1 integers.
08

Perfect Squares FAQ

What is the largest possible answer?

4. Lagrange's four-square theorem says every positive integer is a sum of at most four squares, and Legendre's three-square theorem says exactly the numbers of the form 4ᵃ(8b + 7), such as 7, need all four. Those two facts give an O(√n) maths solution: return 1 if n is a square, 2 if n − k² is a square for some k, 4 for the 4ᵃ(8b + 7) form, else 3.

How is perfect squares LeetCode 279 related to Coin Change?

It is Coin Change (LeetCode 322) where the coins are 1, 4, 9, 16 and so on, the same unbounded knapsack. Because 1 is always a coin, every amount is reachable, so unlike Coin Change the answer is never -1.