Talentd
FleetCode
Make and follow sheets.Sign in

Perfect Squares solution and explanation

MediumBreadth-First SearchDynamic ProgrammingMath4 min read57.0% acceptanceUpdated 9 Apr 2026
Perfect Squares - Dynamic Programming - Leetcode 279 - PythonNeetCode · 15:12 · 75K viewsWatch on YouTube
9 more videos on this problem
  1. Perfect Squares - Dynamic Programming - Leetcode 279 - PythonNeetCode · 15:12 · 75K views
  2. Perfect Squares Dynamic Programming | Leetcode 279 Solution in JAVAPepcoding · 12:41 · 34K views
  3. LeetCode 279. Perfect Squares Explanation and Solutionhappygirlzt · 5:55 · 14K views
  4. Perfect Squares | LeetCode 279 | C++, Java, PythonKnowledge Center · 18:03 · 10K views
  5. 279. Perfect Squares | 5 Ways | DP | Math | BFS | Lagrange & Legendre Theorem | Top-Down | Bottom UpAryan Mittal · 29:30 · 7K views
  6. Leetcode - Perfect Squares (Python)Timothy H Chang · 6:05 · 2K views
  7. [Java] Leetcode 279. Perfect Squares [DP Integer Partition #2]Eric Programming · 11:08 · 2K views
  8. 279. Perfect Squares(Leetcode)EverydayLeetcode · 25:03 · 1K views
  9. Leetcode 279 Perfect SquaresRen Zhang · 10:16 · 1K views
  10. Perfect Squares | Leetcode 279 | Live coding session 🔥🔥🔥Coding Decoded · 11:23 · 1K views

The problem

Given an integer n, return the least number of perfect square numbers that sum to n.

A perfect square is an integer that is the square of an integer; in other words, it is the product of some integer with itself. For example, 1, 4, 9, and 16 are perfect squares while 3 and 11 are not.

Example 1:

Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4.

Example 2:

Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.

Constraints:

  • 1 <= n <= 104

Approach overview

Problem Overview: Given an integer n, return the minimum number of perfect square numbers (like 1, 4, 9, 16...) whose sum equals n. Each square can be used multiple times. The challenge is minimizing the count while exploring combinations efficiently.

Approach 1: Brute Force Recursion (Exponential Time, O(√n) Space)

The brute force approach tries every possible perfect square ≤ n and recursively reduces the remaining value. For each square s, compute 1 + solve(n - s) and track the minimum result. This forms a large recursion tree because the same subproblems (like solve(12) or solve(7)) are recomputed many times. Time complexity grows exponentially in the worst case, roughly O(√n^n), while recursion depth leads to O(√n) auxiliary space. This approach demonstrates the structure of the problem but quickly becomes impractical for larger inputs.

Approach 2: Dynamic Programming (O(n√n) Time, O(n) Space)

The optimized solution uses dynamic programming. Create a DP array where dp[i] stores the minimum number of perfect squares needed to form value i. Initialize dp[0] = 0, then iterate from 1 to n. For each i, try every square j*j ≤ i and update dp[i] = min(dp[i], dp[i - j*j] + 1). This works because every optimal solution for i builds on previously solved smaller states. The algorithm checks about √n squares for each number up to n, resulting in O(n√n) time and O(n) space.

This problem is also commonly interpreted as a shortest-path problem using breadth-first search. Treat each remainder as a node and subtract perfect squares as edges. BFS guarantees the first time you reach 0 corresponds to the minimum number of squares. That approach has similar O(n√n) complexity but models the problem as a graph instead of a DP table.

The core insight comes from number decomposition and math properties of squares. Instead of enumerating combinations blindly, reuse results for smaller numbers. Every value builds on a previously computed optimal solution.

Recommended for interviews: The dynamic programming approach is what most interviewers expect. It clearly demonstrates state definition (dp[i]), transition (dp[i - j*j] + 1), and optimization over previous states. Brute force helps explain the problem structure, but the DP version shows you can eliminate redundant work and reason about time complexity.

Approach 1: Approach 1: Brute Force

In this approach, we solve the problem by checking all possible combinations or configurations naively. Although not efficient for large inputs, this approach is often straightforward and can provide insights into the problem structure.

def solve(n):
    # Brute force in Python
    for i in range(n):
        print(i, end=' ')
    print()

solve(10)
public class Solution {
    public static void solve(int n) {
        // Brute force approach in Java
        for (int i = 0; i < n; i++) {
            System.out.print(i + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        solve(10);
    }
}
#include <iostream>

void solve(int n) {
    // A simple C++ program illustrating brute force
    for(int i = 0; i < n; i++) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int main() {
    solve(10);
    return 0;
}
function solve(n) {
    // Brute force in JavaScript
    for (let i = 0; i < n; i++) {
        process.stdout.write(i + ' ');
    }
    console.log();
}

solve(10);
#include <stdio.h>

void solve(int n) {
    // A simple C program illustrating brute force
    for(int i = 0; i < n; i++) {
        printf("%d ", i);
    }
    printf("\n");
}

int main() {
    solve(10);
    return 0;
}
using System;

class Solution {
    static void Solve(int n) {
        // Brute force approach in C#
        for (int i = 0; i < n; i++) {
            Console.Write(i + " ");
        }
        Console.WriteLine();
    }

    static void Main() {
        Solve(10);
    }
}

In Python, this solution uses a range-based loop to iterate and print each number from 0 to n-1, showing a basic brute force solution.

Complexity

Time Complexity: O(n).
Space Complexity: O(1).

Try this approach in the editor

Approach 2: Approach 2: Optimized Solution using Dynamic Programming

This approach utilizes dynamic programming to optimize the solution by storing interim results and eliminating redundant calculations seen in a brute force approach. This method can significantly improve efficiency when dealing with complex recursive problems.

dp = [-1] * 1000

def solve(n):
    if n <= 1:
        return n
    if dp[n] != -1:
        return dp[n]
    dp[n] = solve(n-1) + solve(n-2)
    return dp[n]

print(solve(10))
import java.util.Arrays;

public class Solution {
    static int[] dp = new int[1000];

    public static int solve(int n) {
        if (n <= 1) return n;
        if (dp[n] != -1) return dp[n];
        return dp[n] = solve(n-1) + solve(n-2);
    }

    public static void main(String[] args) {
        Arrays.fill(dp, -1);
        System.out.println(solve(10));
    }
}
#include <iostream>
#include <vector>

std::vector<int> dp(1000, -1);

int solve(int n) {
    if (n <= 1) return n;
    if (dp[n] != -1) return dp[n];
    return dp[n] = solve(n-1) + solve(n-2);
}

int main() {
    std::cout << solve(10) << std::endl;
    return 0;
}
let dp = Array(1000).fill(-1);

function solve(n) {
    if (n <= 1) return n;
    if (dp[n] !== -1) return dp[n];
    dp[n] = solve(n-1) + solve(n-2);
    return dp[n];
}

console.log(solve(10));
#include <stdio.h>

int dp[1000];

int solve(int n) {
    if (n <= 1) return n;
    if (dp[n] != -1) return dp[n];
    return dp[n] = solve(n-1) + solve(n-2);
}

int main() {
    for (int i = 0; i < 1000; i++) dp[i] = -1;
    printf("%d\n", solve(10));
    return 0;
}
using System;

class Solution {
    static int[] dp = new int[1000];

    static int Solve(int n) {
        if (n <= 1) return n;
        if (dp[n] != -1) return dp[n];
        return dp[n] = Solve(n-1) + Solve(n-2);
    }

    static void Main() {
        Array.Fill(dp, -1);
        Console.WriteLine(Solve(10));
    }
}

Using Python, this solution applies dynamic programming for efficient Fibonacci number calculation, storing results in a list for reuse.

Complexity

Time Complexity: O(n).
Space Complexity: O(n).

Try this approach in the editor

Approach 3: Default Approach

function numSquares(n: number): number {
    const m = Math.floor(Math.sqrt(n));
    const f: number[][] = Array(m + 1)
        .fill(0)
        .map(() => Array(n + 1).fill(1 << 30));
    f[0][0] = 0;
    for (let i = 1; i <= m; ++i) {
        for (let j = 0; j <= n; ++j) {
            f[i][j] = f[i - 1][j];
            if (j >= i * i) {
                f[i][j] = Math.min(f[i][j], f[i][j - i * i] + 1);
            }
        }
    }
    return f[m][n];
}
func numSquares(n int) int {
	m := int(math.Sqrt(float64(n)))
	f := make([][]int, m+1)
	const inf = 1 << 30
	for i := range f {
		f[i] = make([]int, n+1)
		for j := range f[i] {
			f[i][j] = inf
		}
	}
	f[0][0] = 0
	for i := 1; i <= m; i++ {
		for j := 0; j <= n; j++ {
			f[i][j] = f[i-1][j]
			if j >= i*i {
				f[i][j] = min(f[i][j], f[i][j-i*i]+1)
			}
		}
	}
	return f[m][n]
}
impl Solution {
    pub fn num_squares(n: i32) -> i32 {
        let (row, col) = ((n as f32).sqrt().floor() as usize, n as usize);
        let mut dp = vec![vec![i32::MAX; col + 1]; row + 1];
        dp[0][0] = 0;
        for i in 1..=row {
            for j in 0..=col {
                dp[i][j] = dp[i - 1][j];
                if j >= i * i {
                    dp[i][j] = std::cmp::min(dp[i][j], dp[i][j - i * i] + 1);
                }
            }
        }
        dp[row][col]
    }
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach 1: Brute Force

Time Complexity: O(n).
Space Complexity: O(1).

Approach 2: Optimized Solution using Dynamic Programming

Time Complexity: O(n).
Space Complexity: O(n).

Default Approach–

Complexity analysis

ApproachTimeSpaceWhen to Use
Brute Force RecursionExponentialO(√n)Understanding the raw recursive structure of the problem
Dynamic ProgrammingO(n√n)O(n)General case and most common interview solution
Breadth-First SearchO(n√n)O(n)When modeling the problem as shortest path in a state graph

Frequently asked questions

Is Perfect Squares easy or hard?

Perfect Squares is typically classified as a medium difficulty problem. The brute force idea is straightforward, but recognizing the dynamic programming or BFS optimization requires familiarity with state-based problem solving.

Perfect Squares Python/Java solution

In Python or Java, the common implementation builds a dp array of size n+1. For each index i, iterate over square numbers j*j ≤ i and update dp[i] = min(dp[i], dp[i - j*j] + 1). This produces the minimum count in O(n√n) time.

How to solve Perfect Squares in O(n)?

Strict O(n) is generally not achievable using standard DP because each number must consider multiple square candidates. The practical optimal solution is O(n√n) using dynamic programming or BFS, both of which iterate over square numbers for each state.

What is the best approach for Perfect Squares?

Dynamic programming is the most common approach. Define dp[i] as the minimum number of perfect squares that sum to i and compute it using previously solved states. The algorithm runs in O(n√n) time and O(n) space, which works efficiently for typical constraints.

Is Perfect Squares asked at Google/Amazon/Meta?

Perfect Squares is a classic dynamic programming and BFS problem that appears in interviews at companies like Amazon, Google, and Microsoft. It tests state transitions, shortest path reasoning, and understanding of perfect square decomposition.

What data structure is used in Perfect Squares?

The dynamic programming solution uses an integer array dp[] to store minimum counts for each value up to n. The BFS variant uses a queue to explore remainders level by level, similar to shortest path traversal in graphs.

What is the time complexity of Perfect Squares?

The optimized dynamic programming solution runs in O(n√n) time because for every value from 1 to n it checks all square numbers up to √n. Space complexity is O(n) for the DP array storing the minimum counts.

Comments

Now write it yourselfClose this page and solve Perfect Squares in the editor, without looking.
Practise Perfect Squares