# [279] 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4.
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9.
我们注意到,一个数 n 能被完全平方和数相加得出,那么 n 的最大完全平方和的因数一定为 sqrt(n) 后向下取整。
即当前数字 i 的最小平方和组成个数为:i 减去一个比该数小的平方数 j^2 后的数所需要的最小平方和组成个数+1(这个 1 指代的就是 j) 的所有情况的最小值
因此,这就转化为了一个背包容量 n,因数物品种类有限 sqrt(n) 向下取整,数量无限的完全背包问题。
当 n 为 0 时,显然组成其的完全平方数的最小数量为 0 个。
解法显而易见。
function numSquares(n: number): number {
const maxFactor = Math.floor(Math.pow(n, 0.5));
const maxNum = Number.MAX_SAFE_INTEGER;
const dp = Array(maxFactor + 1)
.fill(0)
.map(x => Array(n + 1).fill(maxNum));
for (let i = 1; i <= maxFactor; i++) {
dp[i][0] = 0;
const square = Math.pow(i, 2);
for (let j = 1; j <= n; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= square) {
dp[i][j] = Math.min(dp[i][j], dp[i][j - square] + 1);
}
}
}
}
之后我们发现解法中不需要记录 dp[i],因此省略该复杂度。
function numSquares(n: number): number {
const maxFactor = Math.floor(Math.pow(n, 0.5));
const maxNum = Number.MAX_SAFE_INTEGER;
const dp = Array(n + 1).fill(maxNum);
dp[0] = 0;
for (let i = 1; i <= maxFactor; i++) {
const square = Math.pow(i, 2);
for (let j = 1; j <= n; j++) {
if (j >= square) {
dp[j] = Math.min(dp[j], dp[j - square] + 1);
}
}
}
return dp[n];
}
另外,如果对性能精益求精的话,我们还可以做一些数学优化 -- 四平方和定理。
四平方和定理:任何正整数都能表示为 4 个整数的平方和。
因此我们可以提前做一些优化。
if (n <= 0) return 0;
// 四平方和定理提高算法效率,不知道该定理可以不加这两行
while (n % 4 === 0) n /= 4;
if (n % 8 === 7) return 4;