Perfect Squares solution and explanation
9 more videos on this problem
Perfect Squares - Dynamic Programming - Leetcode 279 - PythonNeetCode · 15:12 · 75K views
Perfect Squares Dynamic Programming | Leetcode 279 Solution in JAVAPepcoding · 12:41 · 34K views
LeetCode 279. Perfect Squares Explanation and Solutionhappygirlzt · 5:55 · 14K views
Perfect Squares | LeetCode 279 | C++, Java, PythonKnowledge Center · 18:03 · 10K views
279. Perfect Squares | 5 Ways | DP | Math | BFS | Lagrange & Legendre Theorem | Top-Down | Bottom UpAryan Mittal · 29:30 · 7K views
Leetcode - Perfect Squares (Python)Timothy H Chang · 6:05 · 2K views
[Java] Leetcode 279. Perfect Squares [DP Integer Partition #2]Eric Programming · 11:08 · 2K views
279. Perfect Squares(Leetcode)EverydayLeetcode · 25:03 · 1K views
Leetcode 279 Perfect SquaresRen Zhang · 10:16 · 1K views
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.
Time Complexity: O(n).
Space Complexity: O(1).
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.
Time Complexity: O(n).
Space Complexity: O(n).
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]
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Approach 1: Brute Force | Time Complexity: O(n). |
| Approach 2: Optimized Solution using Dynamic Programming | Time Complexity: O(n). |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Brute Force Recursion | Exponential | O(√n) | Understanding the raw recursive structure of the problem |
| Dynamic Programming | O(n√n) | O(n) | General case and most common interview solution |
| Breadth-First Search | O(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.
