Perfect Squares Sum [Solution]
Solution 1: Dynamic Programming
Here’s a Python implementation of the num_squares function using dynamic programming:
def num_squares(n: int) -> int:
# Initialize an array to store the minimum number of perfect squares needed for each value up to n
dp = [float('inf')] * (n + 1)
# Base case: 0 perfect squares needed for 0
dp[0] = 0
# Iterate from 1 to n
for i in range(1, n + 1):
# Check all possible perfect squares less than or equal to i
j = 1
while j * j <= i:
dp[i] = min(dp[i], dp[i - j * j] + 1)
j += 1
return dp[n]
# Example usage:
result = num_squares(12)
print(result) # Output: 3
result = num_squares(13)
print(result) # Output: 2
result = num_squares(0)
print(result) # Output: 0
result = num_squares(1)
print(result) # Output: 1
result = num_squares(4)
print(result) # Output: 1
result = num_squares(5)
print(result) # Output: 2
This solution uses dynamic programming to build an array dp,
where dp[i] represents the least number of perfect squares
needed to sum up to i.
Time Complexity:
- The time complexity of this solution is O(
n * sqrt(n)), wherenis the given positive integer. - We iterate over each number from
1tonand for each number, we iterate over perfect square numbers less than or equal to it, which takes O(sqrt(n)) time.
Space Complexity:
- The space complexity is O(
n) because we use an additional dp array of sizen+1.
Solution 2: Breadth-First Search
Another approach to solve this problem is by using Breadth-First Search (BFS) algorithm.
We can consider the given positive integer n as the root.
Here’s how we can implement this approach:
import math
from collections import deque
def num_squares(n: int) -> int:
"""
Return the least number of perfect square numbers that sum up to n.
Parameters:
- n: A positive integer.
Returns:
- int: The least number of perfect square numbers.
"""
# List to store perfect square numbers less than or equal to n
squares = [i * i for i in range(1, int(math.sqrt(n)) + 1)]
# Set to keep track of visited numbers
visited = set()
# Initialize queue for BFS traversal
queue = deque([(n, 0)]) # (number, level)
# Perform BFS traversal
while queue:
num, level = queue.popleft()
for square in squares:
if num - square == 0:
return level + 1
elif num - square > 0 and (num - square) not in visited:
visited.add(num - square)
queue.append((num - square, level + 1))
return 0 # No perfect square numbers sum up to n
# Example usage:
print(num_squares(12)) # Output: 3
print(num_squares(13)) # Output: 2
print(num_squares(0)) # Output: 0
print(num_squares(1)) # Output: 1
print(num_squares(4)) # Output: 1
print(num_squares(5)) # Output: 2
Time Complexity:
Let’s break down the time complexity analysis for the num_squares function:
- Precomputing Perfect Square Numbers:
- We precompute the list of perfect square numbers less than or equal to
n, which requires iterating up to the square root ofn. - This precomputation step takes O(
sqrt(n)) time.
- We precompute the list of perfect square numbers less than or equal to
- Breadth-First Search (BFS) Traversal:
- In the worst case, each number from
nto1is visited once. - For each number, we iterate over the perfect square numbers less than or equal to it.
- Since the number of perfect square numbers less than or equal to
nis also bounded bysqrt(n), the overall time complexity of the BFS traversal is O(n * sqrt(n)).
- In the worst case, each number from
- Overall Time Complexity:
- Combining the time complexity of precomputing perfect square numbers and the BFS traversal, the overall time complexity is dominated by the BFS traversal, which is O(
n * sqrt(n)).
- Combining the time complexity of precomputing perfect square numbers and the BFS traversal, the overall time complexity is dominated by the BFS traversal, which is O(
Therefore, the time complexity of the num_squares function is O(n * sqrt(n)), where n is the given positive integer.
This complexity arises from the iteration over each number from n to 1 and for each number,
the iteration over perfect square numbers less than or equal to it.
Space Complexity:
Let’s analyze the space complexity of the num_squares function:
- Perfect Square Numbers List:
- We create a list of perfect square numbers less than or equal to
n, which requires storing these numbers in memory. - Since the number of perfect square numbers less than or equal to
nis bounded bysqrt(n), the space complexity of storing this list is O(sqrt(n)).
- We create a list of perfect square numbers less than or equal to
VisitedSet:- We use a set to keep track of visited numbers during the BFS traversal to avoid revisiting the same number.
- In the worst case, all numbers from
nto1are visited, so the size of the visited set could be up ton. - Therefore, the space complexity of the visited set is O(
n).
- Queue for BFS Traversal:
- We use a
dequeto perform BFS traversal, which stores tuples of (number, level). - In the worst case, the queue may contain all numbers from
nto1, so the size of the queue could be up ton. - Therefore, the space complexity of the queue is also O(
n).
- We use a
- Overall Space Complexity:
- Combining the space used by the perfect square numbers list,
visitedset, andqueue, the overall space complexity is O(sqrt(n)) + O(n) + O(n), which simplifies to O(n).
- Combining the space used by the perfect square numbers list,
Therefore, the space complexity of the num_squares function is O(n), where n is the given positive integer. This complexity arises from storing the list of perfect square numbers, the visited set, and the queue for BFS traversal.
Solution 3: Lagrange’s Four-square Theorem
There is another solution known as Lagrange’s four-square theorem. This theorem states that every natural number can be represented as the sum of at most four square numbers. Based on this theorem, we can solve the problem as follows:
- Check if the number is a perfect square: If
nitself is a perfect square, then it can be represented as a single perfect square number, so return 1. - Check if the number can be represented as the sum of two perfect squares: If
n - i*iis a perfect square for anyifrom1tosqrt(n), thenncan be represented as the sum of two perfect square numbers. In this case, return2. - Check if the number can be represented as the sum of three perfect squares: This step involves using a double loop. For each
iandj, whereiandjiterate from1tosqrt(n), check ifn - i*i - j*jis a perfect square. If it is, then return3. - If none of the above conditions are met: Return
4, as per Lagrange’s theorem.
Here’s how we can implement this approach:
import math
def num_squares(n: int) -> int:
"""
Return the least number of perfect square numbers that sum up to n.
Parameters:
- n: A positive integer.
Returns:
- int: The least number of perfect square numbers.
"""
# determine whether a number is a perfect square
def perfect_square(m: int) -> bool:
return m == int(math.sqrt(m))**2
# 0 perfect squares needed for 0
if n == 0:
return 0
# Check if n itself is a perfect square
if perfect_square(n):
return 1
# Check if n can be represented as the sum of two perfect squares
sqrt_n = int(math.sqrt(n))
for i in range(1, sqrt_n + 1):
if perfect_square(n - i*i):
return 2
# Check if n can be represented as the sum of three perfect squares
for i in range(1, sqrt_n + 1):
for j in range(1, sqrt_n + 1):
if perfect_square(n - i*i - j*j):
return 3
# If none of the above conditions are met, return 4
return 4
# Example usage:
print(num_squares(12)) # Output: 3
print(num_squares(13)) # Output: 2
print(num_squares(0)) # Output: 0
print(num_squares(1)) # Output: 1
print(num_squares(4)) # Output: 1
print(num_squares(5)) # Output: 2
Time Complexity:
The time complexity of this solution is O(n), where n is the given positive integer.
We perform three iterations:
- The first loop iterates from
1tosqrt(n)to check ifnitself is a perfect square, which takes O(sqrt(n)) time. - The second loop iterates from
1tosqrt(n)to check ifncan be represented as the sum of two perfect squares, which also takes O(sqrt(n)) time. - The third nested loop iterates from
1tosqrt(n)twice to check ifncan be represented as the sum of three perfect squares, which takes O(sqrt(n)*sqrt(n)) = O(n) time.
Therefore, the overall time complexity is dominated by the third loop, resulting in O(n).
Space Complexity:
The space complexity of this solution is O(1), i.e., constant space.
We use a few variables for iteration and calculation, but they don’t depend on the size of the input n.
Hence, the space complexity remains constant, regardless of the value of n.