题目描述
给定一个整数 n,返回和为 n 的完全平方数的最少数量。
完全平方数是一个整数,其值等于另一个整数的平方。例如 1、4、9、16 都是完全平方数,而 3 和 11 不是。
示例:
- 输入:
n = 12→ 输出:3(12 = 4 + 4 + 4) - 输入:
n = 13→ 输出:2(13 = 4 + 9)
解题思路总览
| 方法 | 思路 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 动态规划 | dp[i] 表示和为 i 的最少完全平方数个数,状态转移 dp[i] = min(dp[i], dp[i-j*j]+1) |
O(n√n) | O(n) |
| 数学公式 | 勒让德三平方定理,判断是否能表示为 1/2/3/4 个平方数之和 | O(1) | O(1) |
本题采用动态规划方法。
完整代码
cpp
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
dp[i] = min(dp[i - j * j] + 1, dp[i]);
}
}
return dp[n];
}
};
算法流程图
输入: n = 12
初始化:
dp[0] = 0
dp[1...12] = INT_MAX
i = 1:
j = 1, j*j = 1 <= 1
dp[1] = min(INT_MAX, dp[0]+1) = 1
dp[1] = 1
i = 2:
j = 1, j*j = 1 <= 2
dp[2] = min(INT_MAX, dp[1]+1) = 2
j = 2, j*j = 4 > 2, 退出
dp[2] = 2
i = 3:
j = 1, j*j = 1 <= 3
dp[3] = min(INT_MAX, dp[2]+1) = 3
j = 2, j*j = 4 > 3, 退出
dp[3] = 3
i = 4:
j = 1, j*j = 1 <= 4
dp[4] = min(INT_MAX, dp[3]+1) = 4
j = 2, j*j = 4 <= 4
dp[4] = min(4, dp[0]+1) = 1
dp[4] = 1
... (继续迭代直到 i = 12)
i = 12:
j = 1: dp[12] = dp[11]+1 = 4
j = 2: dp[12] = dp[8]+1 = 3
j = 3: dp[12] = dp[3]+1 = min(3, 4) = 3
j = 4: dp[12] = dp[-4]无效, 跳过 (j*j=16>12)
最终 dp[12] = 3
输出: 3
逐行解析
cpp
vector<int> dp(n + 1, INT_MAX);
含义: 创建大小为 n+1 的数组,dp[i] 表示和为 i 的完全平方数的最少数量。初始化为 INT_MAX 表示尚未计算。
cpp
dp[0] = 0;
含义: 基础情况,和为 0 需要 0 个完全平方数。
cpp
for (int i = 1; i <= n; i++)
含义: 从 1 到 n 依次计算每个数的最少完全平方数个数。
cpp
for (int j = 1; j * j <= i; j++)
含义: 枚举所有可能的完全平方数 j*j,其中 j 从 1 开始,直到 j*j 不超过 i。
cpp
dp[i] = min(dp[i], dp[i - j * j] + 1);
含义: 状态转移方程。如果从 i 中减去一个完全平方数 j*j,那么剩下的 i-j*j 需要 dp[i-j*j] 个平方数,再加上当前的 j*j,总共 dp[i-j*j]+1 个。取较小值更新 dp[i]。
复杂度分析
| 复杂度 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n√n) | 外层循环 n 次,内层循环最多 √n 次(因为 j*j ≤ i) |
| 空间复杂度 | O(n) | 需要大小为 n+1 的 dp 数组 |
面试追问 FAQ
| 问题 | 答案 |
|---|---|
为什么 dp[0] = 0? |
0 的完全平方数之和只能是 0,需要 0 个数字,这是动态规划的基础情况 |
| 能否用 BFS 解决? | 可以,将 n 作为起点,每次减去一个完全平方数,层数即为最少数量 |
| 如何优化到 O(n)? | 使用数学方法:勒让德三平方定理可 O(1) 判断,但实现复杂 |
| 完全平方数有哪些性质? | 平方数 mod 4 结果为 0 或 1,可用于快速判断某个数是否为完全平方数 |
为什么内层循环是 j*j <= i? |
因为 j*j 作为被减数不能超过 i,否则 i-j*j 为负数 |
相关题目
| 题号 | 题目 | 难度 | 核心思路 |
|---|---|---|---|
| 279 | 完全平方数 | 中等 | 动态规划/BFS |
| 322 | 零钱兑换 | 中等 | 完全背包 |
| 300 | 最长递增子序列 | 中等 | 动态规划 |
| 139 | 单词拆分 | 中等 | 完全背包 |
总结
| 要点 | 内容 |
|---|---|
| 核心思想 | 动态规划,枚举所有完全平方数作为最后一个加数 |
| 状态定义 | dp[i] = 和为 i 的最少完全平方数个数 |
| 状态转移 | dp[i] = min(dp[i], dp[i-j*j]+1) |
| 边界条件 | dp[0] = 0 |