题目地址:
https://www.lintcode.com/problem/perfect-squares/description
给定一个正整数 n n n,问其能最少写成多少个完全平方数之和。完全平方数从 1 1 1开始。
法1:数论。设答案是
x
x
x。首先由拉格朗日四平方和定理(Lagrange’s Four-Square Theorem)知道,
x
≤
4
x\le 4
x≤4。注意到如果
4
∣
n
4|n
4∣n,那么有
f
[
n
]
=
f
[
n
/
4
]
f[n]=f[n/4]
f[n]=f[n/4],证明如下:首先可以把
n
/
4
n/4
n/4的分解中的每个平方数乘以
2
2
2得到
n
n
n的一个分解,所以有
f
[
n
]
≤
f
[
n
/
4
]
f[n]\le f[n/4]
f[n]≤f[n/4]。接着,如果存在某个
n
n
n的分解
n
=
a
2
+
b
2
+
c
2
+
d
2
n=a^2+b^2+c^2+d^2
n=a2+b2+c2+d2使得
a
,
b
,
c
,
d
a,b,c,d
a,b,c,d都是偶数,那么显然
f
[
n
/
4
]
≤
f
[
n
]
f[n/4]\le f[n]
f[n/4]≤f[n],结论成立。如果其中有一个是奇数,那么由
4
∣
n
4|n
4∣n知道
f
[
n
]
=
4
f[n]=4
f[n]=4,再由四平方和定理知道
4
=
f
[
n
]
≤
f
[
n
/
4
]
≤
4
4=f[n]\le f[n/4]\le 4
4=f[n]≤f[n/4]≤4,知道
f
[
n
/
4
]
=
4
f[n/4]=4
f[n/4]=4,所以结论也成立。
由上知,可以直接考虑
n
n
n不是
4
4
4的倍数的情况。由勒让德三平方和定理(Legendre’s Three-Square Theorem),
x
=
4
x=4
x=4当且仅当
n
≡
7
(
m
o
d
8
)
n\equiv 7(\mod 8)
n≡7(mod8)(原定理是这么说的,如果某个自然数
n
n
n能表为三个整数的平方和,当且仅当
n
n
n不能写成
4
a
(
8
b
+
7
)
4^a(8b+7)
4a(8b+7)的形式。那么也就是说,如果
4
∤
n
4\nmid n
4∤n,由于
n
n
n能写成少于
3
3
3个整数平方和,必然能写成恰好
3
3
3个整数平方和,补
0
2
0^2
02就行,所以
n
≡
7
(
m
o
d
8
)
⇔
x
=
4
n\equiv 7(\mod 8)\Leftrightarrow x=4
n≡7(mod8)⇔x=4)。如果
n
n
n是完全平方数那么显然
x
=
1
x=1
x=1,如果
n
n
n不是完全平方数但能表为两个平方数之和,显然
x
=
2
x=2
x=2。判断完
x
x
x是否是
1
,
2
,
4
1,2,4
1,2,4之后如果仍然不成立,那么就知道
x
=
3
x=3
x=3了。代码如下:
public class Solution {
public int numSquares(int n) {
// 把4除干净
while (n % 4 == 0) {
n /= 4;
}
// 套用勒让德定理
if (n % 8 == 7) {
return 4;
}
// n是完全平方数,则返回1
if (isSquare(n)) {
return 1;
}
// 判断n是否可以表为两个平方数之和
for (int i = 1; i * i <= n; ++i) {
if (isSquare(n - i * i)) {
return 2;
}
}
// 剩余的情况就是3了
return 3;
}
// 开个函数用于判断n是否是完全平方数
boolean isSquare(int n) {
int sq = (int) Math.sqrt(n);
return n == sq * sq;
}
}
时间复杂度 O ( n / k + k ) O(n/k+k) O(n/k+k),其中 n = 4 s k n=4^sk n=4sk, 4 ∤ k 4\nmid k 4∤k。
法2:动态规划。设 f [ i ] f[i] f[i]为 i i i能表示的最少完全平方数之和的个数。那么有 f [ i ] = min 1 ≤ j 2 ≤ i ( f [ i − j 2 ] + 1 ) f[i]=\min_{1\le j^2\le i} (f[i-j^2]+1) f[i]=min1≤j2≤i(f[i−j2]+1)。代码如下:
import java.util.Arrays;
public class Solution {
/**
* @param n: a positive integer
* @return: An integer
*/
public int numSquares(int n) {
// write your code here
// 稍微做一下优化
while (n % 4 == 0) {
n /= 4;
}
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];
}
}
时间复杂度 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)。