279. 完全平方数(Perfect Squares)¶
频次 ★★★★ · 难度 🟡 · 高频:字节/腾讯
题目¶
给定正整数 n,找出最少个数的完全平方数(1, 4, 9, 16, ...)使其和为 n。
示例:
思路¶
解法 1:完全背包 DP — dp[i] 表示 i 的最少完全平方数个数。dp[i] = min(dp[i], dp[i - j*j] + 1),j*j ≤ i。
解法 2:BFS — 把 n 看作起点,每一步减去一个完全平方数,BFS 层数即为最少个数(求最短路径)。等价于在图中找从 n 到 0 的最短路径。
解法 3:四平方和定理 — 数学方法 O(√n),但面试不要求。
代码¶
// 解法 1:完全背包 DP
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, n + 1); // INF
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];
}
// 解法 2:BFS
public int numSquares(int n) {
Queue<Integer> q = new LinkedList<>();
Set<Integer> visited = new HashSet<>();
q.offer(n);
visited.add(n);
int level = 0;
while (!q.isEmpty()) {
int size = q.size();
level++;
for (int i = 0; i < size; i++) {
int cur = q.poll();
for (int j = 1; j * j <= cur; j++) {
int next = cur - j * j;
if (next == 0) return level;
if (visited.add(next)) q.offer(next);
}
}
}
return level;
}
"最后一步"套路复用与 322 四差异(实测 322→279 平移成本 ~0)¶
f(12) 的"最后一项是谁":= "凑 (12−j*j) 的最短" + 一项 j*j → f(i)=min(f(i−j*j))+1
递归太慢→记忆化=数组;填表 for i=1..n 自增(i−j*j < i 恒旧值,无环每格一次)
| 考点 | 322 | 279 |
|---|---|---|
| 物品 | coins 题面给 | 1,4,9,…现造(j*j≤i) |
| 地基 | dp[0]=0 | 同 |
| 伪∞ | amount+1 | n+1 |
| −1 判定 | 需要 | 不需要(1 兜底必有解) |
复杂度¶
- DP:时间 O(n√n),空间 O(n)
- BFS:时间 O(n√n) 最坏,空间 O(n)
边界条件¶
- n = 1:返回 1
- n 是完全平方数:返回 1
- dp[0] = 0(基值)
变式¶
易错点¶
- 完全平方数是 j*j 不是 j^2(Java 中 ^ 是异或)
- dp 初始化 INF 为 n+1(最坏情况全用 1,共 n 个)
- BFS 解法中 visited 剪枝很重要,否则会超时
面试追问¶
- 四平方和定理是什么? 每个正整数至多由 4 个完全平方数构成。先判断答案是否为 1/2/4,否则为 3。O(√n) 时间,但面试一般不要求
- DP 和 BFS 哪个更好? DP 更直观,BFS 展示图论思维。面试中先 DP 再提 BFS 加分