LeetCode 279. 完全平方数

题目描述

给定一个正整数 n,返回和为 n 的完全平方数的最少数量。

完全平方数是某个整数的平方,例如 1、4、9 和 16。

例如:

复制代码
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4。

原始思路:递归枚举

可以把每个完全平方数看作一次选择。选择一个平方数后,递归计算剩余数还需要多少个平方数。核心是对所有候选方案取最小值,而不是让后一次赋值覆盖前一次结果。

伪代码:

复制代码
函数 dfs(x):
    如果 x <= 0:
        返回 0
    best <- 正无穷
    对每个不超过 x 的完全平方数 square:
        best <- min(best, 1 + dfs(x - square))
    返回 best

对应 Java 代码:

复制代码
class Solution {
    public int dfs(int n) {
        if (n <= 0) return 0;
        int best = Integer.MAX_VALUE;
        for (int j = 1; j <= n; j++) {
            if (Math.sqrt(j) * Math.sqrt(j) == j) {
                best = Math.min(best, dfs(n - j) + 1);
            }
        }
        return best;
    }

    public int numSquares(int n) {
        return dfs(n);
    }
}

这种递归会重复计算相同的剩余值,时间复杂度较高,因此需要缓存状态。

状态定义与转移

定义 dfs(i, j) 为使用前 i 个完全平方数凑出 j 所需的最少数量,其中第 i 个候选平方数为 i*i。

对 i*i 有两种选择:不使用它,转移到 dfs(i - 1, j);使用它,转移到 dfs(i, j - i*i) + 1。使用后仍保留 i,因为完全平方数可以重复使用,例如 12 = 4 + 4 + 4。

复制代码
dfs(i, j) = min(dfs(i - 1, j), dfs(i, j - i*i) + 1)

当 i = 0 时,j = 0 返回 0,j > 0 表示不可达。不可达状态使用 Integer.MAX_VALUE / 2 表示,避免后续加 1 时整数溢出。

记忆化搜索

伪代码:

复制代码
函数 dfs(i, j):
    如果 i == 0:
        如果 j == 0,返回 0
        否则返回 INF
    如果 i*i > j:
        返回 dfs(i - 1, j)
    如果 memo[i][j] 已计算:
        返回 memo[i][j]
    memo[i][j] <- min(dfs(i - 1, j), dfs(i, j - i*i) + 1)
    返回 memo[i][j]

对应 Java 代码:

复制代码
class Solution {
    int[][] memo = new int[101][10001];

    public int dfs(int i, int j) {
        if (i == 0) {
            return j == 0 ? 0 : Integer.MAX_VALUE / 2;
        }
        if (i * i > j) {
            return memo[i][j] = dfs(i - 1, j);
        }
        if (memo[i][j] != 0) {
            return memo[i][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);
    }
}

易错点

1. 直接覆盖最优结果

尝试多个平方数时必须持续取 Math.min,不能只把当前候选直接赋给结果。

2. 共享成员变量 best

每个状态都应有自己的最优值。把 best 作为成员变量会让子问题覆盖父问题的计算,应使用局部变量或直接写入当前 memo[i][j]。

3. 错误减少 i

使用 i*i 后必须递归到 dfs(i, j - i*i),否则会禁止重复使用当前平方数。

4. 无穷大加一溢出

不能让 Integer.MAX_VALUE 参与 + 1,应使用 Integer.MAX_VALUE / 2 或先判断不可达状态。

建议测试用例

复制代码
n = 1  -> 1
n = 4  -> 1
n = 12 -> 3
n = 13 -> 2

复杂度分析

  • 时间复杂度:O(n√n),二维状态数量为 O(n√n)。
  • 空间复杂度:O(n√n),主要来自二维 memo 数组。

复盘

本题的关键是明确两个状态维度、保留当前平方数以支持重复使用,并在所有方案中取最小值。最终递推式为:

复制代码
dfs(i, j) = min(dfs(i - 1, j), dfs(i, j - i*i) + 1)
相关推荐
小蒋学算法6 小时前
算法-交替删除操作后最后剩下的整数-跟着灵茶山艾府学算法
数据结构·算法
荆棘鸟智能7 小时前
林业遥感长势评估怎么做?从NDVI时序分析到LiDAR蓄积量回归
人工智能·算法·智慧林业
hhb_6187 小时前
AIRAGDebug:一键定位RAG链路异常
人工智能·python·算法
朝朝辞暮i8 小时前
C++ 第 6 课:switch —— 多状态选择
开发语言·c++·算法
苏打豆8 小时前
Pinocchio源码阅读——刚体运动物理量空间变换及求导
c++·线性代数·算法·矩阵·机器人
FluxArt9 小时前
GPT Image 2.5 中文提示词教程:Flux Art 可复用模板
前端·gpt·算法
思麟呀9 小时前
位运算:从一个整数到一个集合
c语言·数据结构·c++·算法
阜阳山君GEO9 小时前
豆包 SEED 新版本 GEO 优化:信源金字塔、打分体系与避坑落地指南
人工智能·算法
晓天衡宇•评测社区9 小时前
晓天衡宇亮相 2026 云栖大会:迈向更可信的高质量评测
人工智能·算法·机器学习
CQU_JIAKE9 小时前
9.12[a]
算法