【Hot 100 刷题计划】 LeetCode 279. 完全平方数 | C++ 动态规划 (完全背包)

LeetCode 279. 完全平方数

📌 题目描述

题目级别:中等

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

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

  • 示例 1:

    输入:n = 12

    输出:3

    解释:12 = 4 + 4 + 4

  • 示例 2:

    输入:n = 13

    输出:2

    解释:13 = 4 + 9


💡 破题思路:动态规划的完全背包模型

这道题本质上是一个完全背包问题的变形:

  • 背包容量 :数字 n
  • 物品 :所有的完全平方数(1,4,9,16...1, 4, 9, 16...1,4,9,16...)。
  • 物品重量:完全平方数的数值。
  • 物品价值:数量统统算作 1 个(因为题目要求"最少数量")。
  • 特殊限制:每个物品可以无限次使用。

状态定义:

定义 dp[i] 为:和为 i 的完全平方数的最少数量。

状态转移方程:

对于当前的数字 i,它可以由之前的某个数字加上一个"完全平方数 j×jj \times jj×j"组合而来。

所以我们只需要回头看看之前的状态 dp[i - j*j],然后在这个基础上加 1(也就是加上当前这个完全平方数)。

为了求最少数量,我们在所有可能的回头路径中取最小值:

dp[i] = min(dp[i], dp[i - j * j] + 1)

巧妙的初始化:

  • dp[0] = 0:凑成 0 需要 0 个数。
  • 对于其他的 dp[i],我们需要初始化为一个极大值。这里非常巧妙地初始化为 n + 1。因为即使全用 1 来凑,最多也只需要 n 个数,所以 n + 1 在这里就起到了"无穷大"的作用,同时完美避免了使用 INT_MAX 时发生 INT_MAX + 1 导致的整数溢出问题!

💻 C++ 代码实现

cpp 复制代码
class Solution {
public:
    int numSquares(int n) {
        // 使用变长数组 (或可以替换为 vector<int> dp(n + 1, n + 1))
        int dp[n + 10];
        
        dp[0] = 0;
        // 巧妙初始化为 n + 1,相当于安全版的无穷大
        for (int i = 1; i <= n; i ++ ) dp[i] = n + 1;

        // 遍历背包容量 (从 1 到 n)
        for (int i = 1; i <= n; i ++ )
        {
            // 遍历物品 (完全平方数 j*j)
            for (int j = 1; j * j <= i; j ++ )
            {
                // 状态转移:取当前值与"刨除完全平方数后剩余值的最少数量 + 1"的较小者
                dp[i] = min(dp[i], dp[i - j * j] + 1);
            }
        }

        return dp[n];
    }
};
相关推荐
H Journey2 小时前
C++ 11 新特性 统一初始化与与 std::initializer_list
c++·列表初始化
木子墨5162 小时前
LeetCode 热题 100 精讲 | 动态规划进阶篇:最大子数组和 · 分割等和子集 · 最长公共子序列 · 打家劫舍 III
数据结构·c++·算法·leetcode·动态规划·力扣
li1670902702 小时前
第十章:list
c语言·开发语言·数据结构·c++·算法·list·visual studio
‎ദ്ദിᵔ.˛.ᵔ₎2 小时前
仿函数使用
c++
Z1Jxxx2 小时前
C++ P1150 Peter 的烟
数据结构·c++·算法
是娇娇公主~2 小时前
线程池:工作窃取线程池WorkingStealingPool
c++·线程池
CheerWWW2 小时前
C++学习笔记——函数指针、Lambda表达式、谨慎使用using namespace std、命名空间
c++·笔记·学习
夜猫子ing2 小时前
如何编写一个CMakelists文件
开发语言·c++
踮起脚看烟花2 小时前
chapter10_泛型算法
c++·算法