当前位置: 首页 > ai >正文

【LeetCode 热题 100】279. 完全平方数——(解法三)空间优化

Problem: 279. 完全平方数

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(n * sqrt(n))
    • 空间复杂度:O(n)

整体思路

这段代码同样旨在解决 “完全平方数” 问题,它采用的是一种经过空间优化自底向上(Bottom-Up)动态规划 方法。这是解决此问题最标准和高效的迭代实现之一。

与使用二维DP数组 dp[i][j] 的版本相比,此算法通过一个一维数组 dp[j] 实现了状态压缩,将空间复杂度从 O(N * sqrt(N)) 优化到了 O(N)。

  1. 问题模型:完全背包

    • 此算法的底层模型依然是 完全背包问题
      • 背包容量:目标整数 n
      • 物品:完全平方数 1^2, 2^2, 3^2, ...
      • 目标:用最少的物品(平方数)填满容量为 n 的背包。
  2. 状态定义(一维)

    • 算法定义了一个一维DP数组 dp
    • dp[j] 的含义是:和为 j 的最少完全平方数的数量
    • 这个定义比二维版本更直接,它不关心具体是用了“前几个”平方数,只关心凑成 j 这个结果。
  3. 状态转移与循环设计

    • 初始化
      • dp 数组被初始化为一个极大值(Integer.MAX_VALUE),表示在计算开始前,凑成任何正整数都是“不可能的”。
      • dp[0] = 0 是关键的基础情况,表示凑成和为0需要0个平方数。
    • 遍历顺序
      • 外层循环 for (int i = 1; i * i <= n; i++):这个循环遍历的是“物品”,即每个完全平方数 i*i
      • 内层循环 for (int j = i * i; j <= n; j++):这个循环遍历的是“背包容量”,即目标和 j
    • 状态转移方程
      dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
      这行代码是算法的核心。对于每个目标和 j 和当前考虑的平方数 i*i,它在做决策:
      • dp[j] (在 Math.min 的第一个参数位置):代表不使用当前平方数 i*i 来凑成 j 时,已知的最优解(这个解是基于之前更小的平方数计算得到的)。
      • dp[j - i * i] + 1:代表使用当前平方数 i*i 来凑成 j。如果用了 i*i,那么问题就转化为“用最少的平方数凑成 j - i*i”,其解是 dp[j - i*i],然后再加1(因为我们用掉了 i*i 这一个数)。
    • 完全背包的体现:内层循环 j 是从 i*i 从小到大遍历的。这确保了在计算 dp[j] 时,我们所依赖的 dp[j - i*i] 可能是在本次外层循环中刚刚被更新过的。这意味着同一个平方数 i*i 的贡献可以被累加,从而实现了“完全背包”中物品可无限次使用的特性。
  4. 返回结果

    • 当所有循环结束后,dp[n] 中就存储了用所有小于等于 n 的平方数凑成 n 所需的最少数量。

完整代码

import java.util.Arrays;class Solution {/*** 计算和为 n 的最少完全平方数的数量(空间优化版)。* @param n 目标整数* @return 最少完全平方数的数量*/public int numSquares(int n) {// dp: 一维动态规划数组。// dp[j] 表示和为 j 的最少完全平方数的数量。int[] dp = new int[n + 1];// 初始化:假设凑成任何数都需要无穷多个平方数。Arrays.fill(dp, Integer.MAX_VALUE);// 基础情况:和为 0 需要 0 个平方数。dp[0] = 0;// 外层循环:遍历所有可用的“物品”,即完全平方数。// i*i 是当前考虑的平方数。for (int i = 1; i * i <= n; i++) {// 内层循环:遍历所有可能的“背包容量”,即目标和 j。// j 从 i*i 开始,因为小于 i*i 的 j 不可能包含 i*i。for (int j = i * i; j <= n; j++) {// 状态转移方程:// 决策是否使用 i*i 这个平方数来构成 j。// dp[j] 的新值 = min(不使用 i*i 的最优解, 使用 i*i 的最优解)// dp[j] (旧值)         -> 不使用 i*i 的情况// dp[j - i*i] + 1      -> 使用 i*i 的情况dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}// 循环结束后,dp[n] 中存储的就是最终答案。return dp[n];}
}

时空复杂度

时间复杂度:O(n * sqrt(n))

  1. 循环结构:算法使用了嵌套循环。
    • 外层 for 循环的 i1 增长到 (int)Math.sqrt(n)。因此,它执行了大约 sqrt(n) 次。
    • 内层 for 循环的 ji*i 遍历到 n。其执行次数为 n - i*i + 1
  2. 计算依据
    • 总的操作次数是所有内层循环执行次数的总和。
    • 总次数 ≈ Σ (从 i=1sqrt(n)) of (n - i*i)。
    • 这是一个多项式求和,其最高阶项由 n * sqrt(n) 主导。

综合分析
算法的时间复杂度为 O(n * sqrt(n))

空间复杂度:O(n)

  1. 主要存储开销:算法创建了一个名为 dp 的一维整型数组。
  2. 空间大小:该数组的长度是 n + 1。其大小与输入 n 成线性关系。
  3. 其他变量i, j 等变量只占用常数级别的空间,即 O(1)。

综合分析
算法所需的额外空间主要由 dp 数组决定。因此,其空间复杂度为 O(n)。这是对二维DP解法 O(n * sqrt(n)) 空间的显著优化。

参考灵神

http://www.xdnf.cn/news/18221.html

相关文章:

  • 应用在运行时,向用户索取(相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求--教你如何解决华为市场上架难题
  • 手机截图如何优雅地放在word里
  • Hangfire定时部署(.NET 8 + SQL Server)
  • 读者写者问题
  • Linux多线程——线程池
  • Spark学习
  • MySQL基础操作
  • 网络连接的核心机制
  • HTML+CSS:浮动详解
  • Python 文件操作与异常处理全解析
  • Zemax光学设计输出3D
  • idea进阶技能掌握, 使用自带HTTP测试工具,完全可替代PostMan
  • OpenSSH 命令注入漏洞(CVE-2020-15778)修复,升级openssh9.8p1
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(一)基本代码
  • Qt设置软件使用期限【新版防修改系统时间】
  • React响应式链路
  • 【蒸蒸日上】专栏前言
  • Google Chrome v139.0.7258.139 便携增强版
  • 云手机在社交媒体场景中的优势体现在哪些方面?
  • 趣打印高级版--手机打印软件!软件支持多种不同的连接方式,打印神器有这一个就够了!
  • AutoGLM2.0背后的云手机和虚拟机分析(非使用案例)
  • Claude Code NPM 包发布命令
  • 数据挖掘笔记:点到线段的距离计算
  • GitHub宕机生存指南:从应急协作到高可用架构设计
  • [TryHackMe]Mr Robot CTF(hydra爆破+Wordpress更改主题)
  • Leetcode 深度优先搜索 (9)
  • MPR多平面重建一:初步实现
  • linux报permission denied问题
  • 【C语言16天强化训练】从基础入门到进阶:Day 4
  • 创建Vue项目的不同方式及项目规范化配置