1. 题目描述
给定一个整数 n
,求 最少需要多少个完全平方数的和 才能表示 n
。
完全平方数 是指某个整数的平方,例如 1, 4, 9, 16, 25
等。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9
2. 解题思路(DFS + 记忆化搜索)
本题类似于 背包问题,我们可以使用 DFS(深度优先搜索)+ 记忆化搜索 来进行求解。
思路分析
-
递归搜索所有可能的完全平方数组合。
-
由于可能存在大量重复计算,因此使用 记忆化搜索(Memoization) 进行优化。
-
递归状态
dfs(i, j)
:-
i
代表当前可使用的最大完全平方数i^2
。 -
j
代表当前剩余的目标值。 -
目标是找到 最少 需要多少个平方数。
-
代码实现
import java.util.Arrays;
class Solution {
// 记忆化数组 memo[i][j] 代表使用前 i 个平方数凑出 j 的最少数量
private static final int[][] memo = new int[101][10001];
static {
for (int[] row : memo) {
Arrays.fill(row, -1);
}
}
private static int dfs(int i, int j) {
if (i == 0) {
return j == 0 ? 0 : Integer.MAX_VALUE;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
if (j < i * i) {
return memo[i][j] = dfs(i - 1, j);
}
return memo[i][j] = Math.min(dfs(i - 1, j), dfs(i, j - i * i) + 1);
}
public int numSquares(int n) {
return dfs((int) Math.sqrt(n), n);
}
}
代码解析
-
初始化
memo
记忆化数组-
memo[i][j]
代表 使用前i
个平方数 组合成j
需要的最小数量。 -
memo
预设为-1
,表示未计算。
-
-
递归
dfs(i, j)
逻辑-
终止条件:
-
i == 0
且j == 0
,返回0
(无需任何数)。 -
i == 0
且j != 0
,返回Integer.MAX_VALUE
(无解)。
-
-
记忆化查询:如果
memo[i][j]
已计算,则直接返回。 -
不选
i^2
的情况:递归dfs(i - 1, j)
。 -
选择
i^2
的情况:递归dfs(i, j - i^2) + 1
。 -
返回最小值,即
min(不选, 选)
。
-
-
numSquares(n)
主函数-
Math.sqrt(n)
获取最大可选完全平方数。 -
调用
dfs(i, n)
计算最优解。
-
3. 其他解法对比
方法 1:动态规划(DP)
思路:
-
定义
dp[j]
:表示和为j
的最少完全平方数个数。 -
状态转移方程:
dp[j] = Math.min(dp[j], dp[j - k^2] + 1)
其中
k^2 ≤ j
。
代码实现:
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 * i <= n; i++) {
int square = i * i;
for (int j = square; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - square] + 1);
}
}
return dp[n];
}
时间复杂度:O(n√n)
方法 2:数学解法(拉格朗日四平方和定理)
定理:任何正整数 n
,最少可以由 4
个或更少的完全平方数组成。
优化步骤:
-
n
是完全平方数,返回1
。 -
检查
n
是否可以由2
个平方数组成,如n = 13
。 -
检查
n
是否满足n = 4^a * (8b + 7)
,如果是则返回4
。 -
否则返回
3
。
代码实现:
public int numSquares(int n) {
while (n % 4 == 0) {
n /= 4;
}
if (n % 8 == 7) {
return 4;
}
for (int i = 0; i * i <= n; i++) {
int j = (int) Math.sqrt(n - i * i);
if (i * i + j * j == n) {
return i == 0 || j == 0 ? 1 : 2;
}
}
return 3;
}
时间复杂度:O(√n)
,更优!
4. 总结
方法 | 时间复杂度 | 适用情况 |
---|---|---|
DFS + 记忆化搜索 | O(n√n) | 适用于小规模 n ,但递归深度大,可能超时 |
动态规划 | O(n√n) | 适用于中等规模 n ,但需要额外 O(n) 空间 |
数学优化 | O(√n) | 适用于所有 n ,最优方法 |
推荐解法:
-
如果
n
较小,DFS + 记忆化搜索或 DP 都可以。 -
如果
n
较大,数学方法最优。
如果觉得这篇文章对你有帮助,记得 点赞👍、收藏⭐、关注📌 哦!
作者:gentle_ice 🚀 CSDN