【动态规划-11】279.完全平方数

题目描述:

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

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

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

示例 2:

复制代码
输入:n = 13
输出:2
解释:13 = 4 + 9

解题思路:

方法一:动态规划(完全背包)

核心思路:

状态定义:

dp[i] = 凑出 i 所需的最少完全平方数数量。

状态转移:

对于每个 i,枚举所有小于等于 i 的完全平方数 j²:

复制代码
dp[i] = min(dp[i], dp[i - j²] + 1)
初始化:
  • dp[0] = 0(凑出 0 需要 0 个)

  • 其他初始化为 INT_MAX 或 i(最坏情况全用 1)

具体过程示例:

n = 12

复制代码
dp[0] = 0
dp[1] = dp[0] + 1 = 1(1 = 1)
dp[2] = dp[1] + 1 = 2(2 = 1+1)
dp[3] = dp[2] + 1 = 3(3 = 1+1+1)
dp[4] = min(dp[3]+1, dp[0]+1) = 1(4 = 4)
dp[5] = min(dp[4]+1, dp[1]+1) = 2(5 = 4+1)
...
dp[12] = min(dp[11]+1, dp[8]+1, dp[3]+1)
       = min(?, ?, 3+1) = 3(12 = 4+4+4)✅

代码实现:

写法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], dp[i - j * j] + 1);
            }
        }
        
        return dp[n];
    }
};
写法2:先遍历平方数(完全背包思路)
cpp 复制代码
class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        
        for (int j = 1; j * j <= n; j++) {
            int square = j * j;
            for (int i = square; i <= n; i++) {
                dp[i] = min(dp[i], dp[i - square] + 1);
            }
        }
        
        return dp[n];
    }
};

两种写法的区别:

  • 写法1:外层遍历 i,内层遍历平方数

  • 写法2:外层遍历平方数,内层遍历 i(标准完全背包)

本题两种都可以 ,因为求的是组合数(顺序不重要)。

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n × √n) 外层 n,内层最多 √n 个平方数
空间复杂度 O(n) dp 数组

关键细节:

1. 为什么 dp[0] = 0?

凑出 0 不需要任何平方数。

2. 为什么初始化为 INT_MAX?

因为要求最少 数量,初始化为最大值方便取 min。

3. 为什么内层循环是 j * j <= i?

因为只需要考虑小于等于 i 的平方数。

4. 和「零钱兑换」的区别
题目 区别
322. 零钱兑换 硬币面额任意,求最少硬币数
279. 完全平方数 面额是平方数,求最少数量

本质完全一样,都是完全背包求最少数量。

方法二:BFS(层序遍历)

思路:

把 n 看作起点,每次减去一个平方数,看最少几步到达 0。

代码实现:

cpp 复制代码
class Solution {
public:
    int numSquares(int n) {
        queue<int> q;
        q.push(n);
        vector<bool> visited(n + 1, false);
        visited[n] = true;
        int level = 0;
        
        while (!q.empty()) {
            int size = q.size();
            level++;
            
            for (int i = 0; i < size; i++) {
                int curr = q.front();
                q.pop();
                
                for (int j = 1; j * j <= curr; j++) {
                    int next = curr - j * j;
                    if (next == 0) return level;
                    if (!visited[next]) {
                        visited[next] = true;
                        q.push(next);
                    }
                }
            }
        }
        
        return level;
    }
};

复杂度:时间 O(n × √n),空间 O(n)

方法三:数学(拉格朗日四平方定理)

定理 :每个正整数都可以表示为至多四个平方数之和。

所以答案只可能是 1、2、3、4。

  • 答案是 1:n 本身是完全平方数

  • 答案是 2:n = a² + b²

  • 答案是 4:n = 4^k × (8m + 7)

  • 否则答案是 3

代码实现:

cpp 复制代码
class Solution {
public:
    int numSquares(int n) {
        // 答案是 1
        int sqrtN = sqrt(n);
        if (sqrtN * sqrtN == n) return 1;
        
        // 答案是 4
        while (n % 4 == 0) n /= 4;
        if (n % 8 == 7) return 4;
        
        // 答案是 2
        for (int i = 1; i * i <= n; i++) {
            int j = sqrt(n - i * i);
            if (i * i + j * j == n) return 2;
        }
        
        // 答案是 3
        return 3;
    }
};

复杂度:时间 O(√n),空间 O(1)

三种方法对比:

方法 时间复杂度 空间复杂度 推荐度
动态规划 O(n × √n) O(n) ⭐⭐⭐⭐⭐
BFS O(n × √n) O(n) ⭐⭐⭐⭐
数学定理 O(√n) O(1) ⭐⭐⭐⭐

总结:

要点 说明
核心思想 完全背包,dp[i] = min(dp[i], dp[i-j²]+1)
初始化 dp[0] = 0,其余 INT_MAX
时间复杂度 O(n × √n)
空间复杂度 O(n)
相关推荐
淡海水1 小时前
02-06-原理篇-增量式GC与并发GC
算法·unity·c#·并发·gc·增量
对空六课1 小时前
无头浏览器访问对跳出率的过滤规则
服务器·前端·算法·数据分析
有同事要进步1 小时前
leecode第一题-两数字之和(打卡)
算法
程序员清风1 小时前
二叉搜索树:查找、插入与删除
算法
HRTOS1 小时前
HRTOS 内核 BUG 修复:任务等待对象未初始化导致的异常唤醒问题
单片机·嵌入式硬件·算法·51单片机
瑶山1 小时前
Windows + WSL2 Ubuntu + VS Code配置C++开发环境
开发语言·c++·windows·vscode·wsl
XMAIPC_Robot1 小时前
国产FPGA替代Xilinx在储能电力采集场景可行性评估与移植经验
人工智能·算法·机器学习·fpga开发·rk3588+fpga
开开心心就好2 小时前
一元二次方程求解工具,答案带详细步骤
java·服务器·开发语言·算法·pdf·ocr·perl
小米里的大麦2 小时前
19 C、C++ 操作 MySQL
c语言·c++·mysql