Leetcode 279. Perfect Squares
Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.
Example 1:
Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4.
Example 2:
Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.
解法一 动态规划
第一步 定义状态
dp[i]
表示给定i
的最少完全平方数的数目
第二步 初始化
初始化dp[i]
为最大值。
第三步 状态转移方程
dp[0] = 0
dp[1] = dp[0]+1 = 1
dp[2] = dp[1]+1 = 2
dp[3] = dp[2]+1 = 3
dp[4] = Min{ dp[4-1*1]+1, dp[4-2*2]+1 }
= Min{ dp[3]+1, dp[0]+1 }
= 1
dp[5] = Min{ dp[5-1*1]+1, dp[5-2*2]+1 }
= Min{ dp[4]+1, dp[1]+1 }
= 2
.
.
dp[13] = Min{ dp[13-1*1]+1, dp[13-2*2]+1, dp[13-3*3]+1 }
= Min{ dp[12]+1, dp[9]+1, dp[4]+1 }
= 2
.
.
dp[n] = Min{ dp[n - i*i] + 1 }, n - i*i >=0 && i >= 1
public int numSquares(int n) {
int[] dp = new int[n+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j * j <= i; j++){
dp[i] = Math.min(dp[i], dp[i - j*j] + 1);
}
}
return dp[n];
}
解法二 BFS
简单地说 就是层次遍历 求第一个满足条件的节点的层次
- 初始化队列
queue=[n]
,访问元组visited={}
,初始化路径长度step = 0
- 如果
n == 0
,返回0。 - 循环条件:队列不为空
step += 1
:循环一次意味着一层的节点遍历完毕,路径长度加一。- 定义当前层中的节点数
l = len(queue)
,遍历当前层所所有节点:- 取出当前队首元素
tmp
。 - 遍历所有可能数
i
的平方数,遍历区间 [ 1 , i n t ( t m p ) + 1 ) [1,int(\sqrt{tmp})+1) [1,int(tmp)+1):- 定义 x = t m p − i 2 x = tmp - i^2 x=tmp−i2
- 若
x == 0
,则返回当前路径长度。 - 若
x not in visited
,表示当前节点没有出现过,将该节点入队并加入visited
数组。
- 取出当前队首元素
- 返回
step
class Solution:
def numSquares(self, n: int) -> int:
from collections import deque
if n == 0: return 0
queue = deque([n])
step = 0
visited = set()
while(queue):
step+=1
l=len(queue)
for _ in range(l):
tmp=queue.pop()
for i in range(1,int(tmp**0.5)+1):
x=tmp-i**2
if(x==0):
return step
if(x not in visited):
queue.appendleft(x)
visited.add(x)
return step
解法三 拉格朗日四平方和定理
定理:每个正整数均可表示成不超过四个整数的平方之和
重要的推论:
- 数
n
如果只能表示成四个整数的平方和,不能表示成更少的数的平方之和,则必定满足 n = 4 a ( 8 b + 7 ) n=4^a(8b+7) n=4a(8b+7) - 如果
n%4 == 0
,k = n/4
,n
和k
可由相同个数的整数表示。
如何利用推论求一个正整数最少需要多少个数的平方和表示:
- 先判断这个数是否满足 n = 4 a ( 8 b + 7 ) n=4^a(8b+7) n=4a(8b+7),如果满足,那么这个数就至少需要 4 个数的平方和表示,即答案为4。
- 如果不满足,再在上面除以 4 之后的结果上暴力尝试只需要 1 个数就能表示和只需要 2 个数就能表示的情况。
- 如果这个数本来就是某个数的平方,那么答案就是1
- 如果答案是2的话,那么 n = a 2 + b 2 n=a^2+b^2 n=a2+b2,枚举 a 即可
- 如果还不满足,那么就只需要 3 个数就能表示
将输入的n迅速缩小。然后再判断,这个缩小后的数是否可以通过两个平方数的和或一个平方数组成,不能的话我们返回3,能的话我们返回平方数的个数。
public int numSquares(int n) {
while (n % 4 == 0){
n /= 4;
}
if ( n % 8 == 7){
return 4;
}
int a = 0;
while ( (a * a) <= n){
int b = (int)Math.pow((n - a * a),0.5);
if(a * a + b * b == n) {
//如果可以 在这里返回
if(a != 0 && b != 0) {
return 2;
} else{
return 1;
}
}
a++;
}
return 3;
}
更多解法
Summary of 4 different solutions (BFS, DP, static DP and mathematics)
Static DP, C++ 12 ms, Python 172 ms, Ruby 384 ms