LeetCode 279. 完全平方数

题目描述

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

完全平方数是某个整数的平方,例如 14916

例如:

复制代码
输入: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 返回 0j > 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)
相关推荐
尾善爱看海1 小时前
前端算法与手写题集
前端·算法
程序员阿鹏2 小时前
为什么MySQL InnoDB选择B+树?
数据结构·数据库·b树·sql·mysql·算法·缓存
AI 思录2 小时前
Prompt 事故档案(八):日常表达被标为“待校准”,AI 的爹味语法从哪里来
大数据·人工智能·算法·prompt·用户体验·ai合规
月光船幽幽2 小时前
跨范式映射的稳定接口设计
人工智能·python·算法
2601_965742223 小时前
全媒体运营与短视频代运营,两者有什么区别?
大数据·数据结构·人工智能·算法·ai·媒体
wordbaby4 小时前
混合检索:两全其美的艺术
人工智能·算法
彧azz5 小时前
数据结构:关于图的学习
c语言·数据结构·笔记·学习·算法
热心网友俣先生5 小时前
A题-药材的烘干问题-题意逐句翻译
算法·数学建模
2601_960356385 小时前
2026秋招运营分析师能力模型:SQL、指标体系、AIGC与业务分析
算法
hetao17338375 小时前
校内场-提高组 ZYZSC-S-Round 4
c++·算法