Leetcode 完全平方数

news2025/4/28 16:54:58

在这里插入图片描述
这段代码是用 动态规划(Dynamic Programming, DP)来解决 LeetCode 第279题「完全平方数」的问题,题目要求给定一个整数 n,找出若干个完全平方数(如1, 4, 9, 16等)的和,恰好等于 n,并且这些数的个数最少。

代码的算法思想可以总结为以下几个步骤:

1. 初始化 DP 数组

我们创建一个长度为 n+1 的数组 dp,其中 dp[i] 表示能够凑成数字 i 的最少完全平方数的个数。初始化时,将 dp[0] 设为 0,表示凑成数字 0 需要 0 个完全平方数。而其余所有位置的值都初始化为 Integer.MAX_VALUE,表示初始状态下我们还不知道如何凑成这些数值(我们用一个较大的数来初始化,方便后面取最小值)。

2. 预处理所有的完全平方数

我们需要找到小于或等于 n 的所有完全平方数,比如对于 n=12,我们需要考虑的完全平方数是 1, 4, 9(即 1^2, 2^2, 3^2)。代码通过循环 for (int i = 1; i * i <= n; i++) 来生成这些完全平方数。

3. 更新 DP 数组

对于每一个完全平方数 square = i * i,我们从 square 开始更新 DP 数组。更新的规则是:
dp[j] = Math.min(dp[j], dp[j - square] + 1),意思是我们要凑成数字 j,可以通过之前凑成 j - square 的数字,再加上一个 square 来凑成 j,因此我们选择最小的完全平方数组合个数。

例如,假设我们当前有 n=12,其中 square=4(即 2^2),我们可以通过 dp[12] = Math.min(dp[12], dp[12 - 4] + 1) 来更新 dp[12] 的值。

4. 最终结果

当所有完全平方数都被处理完后,dp[n] 中存储的就是凑成 n 的最少完全平方数的个数。程序最后返回 dp[n] 即可。

举例说明:

对于 n = 12

  • 1^2 = 1, 2^2 = 4, 3^2 = 9
  • 我们可以得到的最优解是 4 + 4 + 4,即 dp[12] = 3,需要三个完全平方数。

对于 n = 13

  • 我们可以得到的最优解是 9 + 4,即 dp[13] = 2,需要两个完全平方数。

该算法的时间复杂度:

  • 外层循环运行 sqrt(n) 次,内层循环运行 n 次。因此时间复杂度为 O(n * sqrt(n)),这对于较大的 n 也是一个相对高效的解法。

这个算法的核心思想是利用动态规划,通过之前的计算结果来优化后续的计算,使得每个数 n 都能以最少的完全平方数组合凑成。

java 实现代码:

class Solution {
    public int numSquares(int n) {
        //使用动态规划算法,dp[i]表示凑成和为i所需要的完全平方数的最少数量。
        //首先创建长度为n+1的数组
        int[] dp = new int[n + 1];
        //由于后面需要使用min更新dp, 所以我们初始时将所有元素设为Integer.MAX_VALUE
        Arrays.fill(dp, Integer.MAX_VALUE);
        //初始化dp[0],凑成0需要0个
        dp[0] = 0;

        //然后开始更新dp
        for(int i = 1; i * i <= n; ++i) { 
            //i 只是用来生成平方数的根,用于计算出完全平方数,它并不是我们要更新的目标数。
            //寻找所有不大于 n 的完全平方数
            int square = i * i; //square从1开始
            for(int j = square; j <=n; j++) {
                //这里更新的是dp[j]而不是dp[i],为什么?
                //j 表示当前我们要凑成的数。在内部循环中,我们尝试使用平方数 square 来更新所有大于等于 square 的数字 j
                //dp[j - square] 表示我们已经凑成了 j - square,接下来再加上一个 square 来凑成 j。
                dp[j] = Math.min(dp[j], dp[j - square] + 1);
            }
        }
        return dp[n];
    }
}

对于这一部分代码,为什么更新dp数组时使用dp[j]而不是dp[i]?

        // Precompute all the perfect squares less than or equal to n
        for (int i = 1; i * i <= n; i++) {
            int square = i * i;
            // Update the dp array
            for (int j = square; j <= n; j++) {
                dp[j] = Math.min(dp[j], dp[j - square] + 1);
            }
        }

这是一个非常好的问题!我们需要仔细理解这段代码中的变量 ij 的含义,特别是在动态规划的过程中,为什么我们更新的是 dp[j] 而不是 dp[i]

1. 变量的含义

  • i:循环中的 i 代表生成完全平方数的根。例如,i = 1 代表平方数 1^2 = 1i = 2 代表平方数 2^2 = 4,以此类推。
  • square:这是由 i 计算出的平方数,即 square = i * i,它是我们用来凑成数字 j 的完全平方数。
  • jj 代表我们正在更新的数字(目标数)。我们试图使用之前计算出的较小的平方数来凑成 j

2. 为什么更新 dp[j] 而不是 dp[i]

  • i 只是用来生成平方数的根,用于计算出完全平方数 square,它并不是我们要更新的目标数。我们要更新的是那些可以被凑成的数字 j
  • j 表示当前我们要凑成的数。在内部循环中,我们希望尝试使用平方数 square 来更新所有大于等于 square 的数字 j(即 j = square, square + 1, ..., n)。

举个例子来说明:

举例:

假设 n = 12,我们已经生成了完全平方数 square = 4(即 i = 2square = 2^2 = 4),接下来要更新 DP 数组。

  • 我们的目标是通过这个 square = 4 来凑成所有可能的 j,从 4 开始到 12
  • 例如,我们想通过 square = 4 来更新 dp[5]。我们知道 dp[5 - 4] = dp[1],而 dp[1] 是已经计算好的,因此我们可以通过 dp[5] = Math.min(dp[5], dp[1] + 1) 来更新 dp[5],意思是我们可以用 4 + 1 来凑成 5,这是一种新的组合方式。

通过从 square 开始更新所有数字 j,我们可以确保每一个 j 都会考虑到使用平方数 square 来组合的情况,从而找到最优解。

3. 为什么不用 dp[i]

如果你使用 dp[i],这就意味着你只在更新由 i 直接生成的那个完全平方数,而不是更新所有可能用这个平方数 square 凑成的其他数。例如:

  • 如果你只更新 dp[i],那只会更新 dp[i * i],即你只会更新 dp[1], dp[4], dp[9],而不会更新 dp[5], dp[6], ...,因此很多数的最优解都无法被计算出来。
  • 但是在题目中,很多数(比如 12)可以由多个平方数组合出来(如 12 = 4 + 4 + 4),如果你只更新 dp[i] 而不是 dp[j],你就无法找到这些组合的最优解。

4. 内外循环的意义

  • 外层循环for (int i = 1; i * i <= n; i++) 负责生成所有可能的完全平方数 square = i * i。这些平方数是我们凑成目标数的工具。
  • 内层循环for (int j = square; j <= n; j++) 负责更新 DP 数组中每个数 j,我们尝试用平方数 square 去凑成这个数 j,并更新 dp[j]

5. 总结

  • dp[j] 是我们正在尝试更新的目标数,代表凑成 j 所需的最少完全平方数个数。
  • i 只是用来生成平方数 square 的变量,它并不是我们要更新的目标,因此我们不使用 dp[i]
  • 我们通过遍历所有可能的 j,利用之前计算好的较小数的最优解来更新当前数 j 的最优解,确保找到最少的平方数组合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2204876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【韩顺平Java笔记】第8章:面向对象编程(中级部分)【314-326】

314. 动态绑定机制 【注】属性没有动态绑定机制 虽然B类sum被注释掉了&#xff0c;但是其父类A类的sum方法没被注释掉&#xff0c;所以调用的是A类的sum方法&#xff0c;A类的sum方法中有个getI()方法&#xff0c;根据动态绑定机制&#xff0c;现在这个a引用的运行类型是B类&a…

【CTF Web】Pikachu getimagesize() Writeup(文件上传+PHP+文件包含漏洞+JPEG图片马+getimagesize绕过)

不安全的文件上传漏洞概述 文件上传功能在web应用系统很常见&#xff0c;比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后&#xff0c;后台会对上传的文件进行判断 比如是否是指定的类型、后缀名、大小等等&#xff0c;然后将其按照设计的格式进行重…

mongodb 数据迁移,亲测成功!

mysql进行数据迁移&#xff0c;最简单的不过是导出sql&#xff0c;然后在运行sql&#xff0c;数据也自然迁移过去了。 可是mongodb里&#xff0c;我们存储的是文件&#xff0c;是怎么做到的呢&#xff0c;当我在翻阅网上博客的时候&#xff0c;并没有发现有这方面的顾虑。 当…

制造业DT数字化之生产制造业务建模

一、工厂建模为何物&#xff1f; 对制造业人员&#xff08;人&#xff09;、设备&#xff08;机&#xff09;、材料&#xff08;料&#xff09;、工艺流程&#xff08;法&#xff09;、工厂环境&#xff08;环&#xff09;数据化管理的过程就叫工厂建模。 二、制造建模有哪几大…

Math.js 进阶使用:数值比较和数学运算

一. 引言 上篇文章中&#xff0c;我们了解了 Math 工具函数在数值操作方面的妙用&#xff0c;主要包括&#xff1a;取绝对值、向上向下取整以及四舍五入的方法&#xff0c;详细了解请参考上一篇文章&#xff1a; Math 工具函数的妙用&#xff1a;常用的数值操作 本篇文章将继…

【Linux系统编程】第三十弹---软硬链接与动静态库的深入探索

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、软硬链接 1.1、见一见 1.2、特征 1.3、什么是软硬链接&#xff1f;有什么用(为什么)&#xff1f; 2、动态库和静态库 2.…

从 Vim 到 VSCode:提升远程开发效率的秘密武器

1.前言 在 Linux 服务器上进行开发时&#xff0c;我们常常会选择使用 vi 或 vim 这些轻量级的编辑器。虽然它们可以满足基本的编辑需求&#xff0c;但在处理大型项目时&#xff0c;局限性很明显&#xff1a;缺乏现代编辑器的高级功能&#xff0c;比如语法高亮、代码补全以及便…

windows 调整虚拟内存文件大小,释放C盘

Windows 虚拟内存文件&#xff08;通常是 pagefile.sys&#xff09;的作用是充当物理内存&#xff08;RAM&#xff09;的扩展&#xff0c;当系统内存不足时&#xff0c;它为系统提供一个额外的、基于硬盘的存储空间。这种虚拟内存的机制帮助系统在物理内存耗尽时仍能继续运行程…

数据结构 ——— 单链表oj题:相交链表(链表的共节点)

目录 题目要求 手搓两个相交简易链表 代码实现 题目要求 两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单链表相交的起始节点&#xff0c;如果两个链表不存在相交节点&#xff0c;则返回 NULL 手搓两个相交简易链表 代码演示&#xff1a; struct Lis…

SpringSecurity(一)——认证实现

一、初步理解 SpringSecurity的原理其实就是一个过滤器链&#xff0c;内部包含了提供各种功能的过滤器。 当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。 核心过滤器&#xff1a; &#xff08;认证&#xff09;UsernamePasswordAuthenticationFilter:负责处理…

LabVIEW提高开发效率技巧----状态保存与恢复

在LabVIEW开发中&#xff0c;保存和恢复程序运行时的状态是一个关键技巧&#xff0c;特别是在涉及需要暂停或恢复操作的应用中。通过使用 Flatten To String 和 Unflatten From String 函数&#xff0c;开发人员可以将程序当前的状态转换为字符串并保存&#xff0c;再在需要时恢…

Vue入门-指令修饰符-事件修饰符

事件修饰符 事件名.stop ->阻止冒泡 demo&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><ti…

【Redis】Set类型常用命令

目录 一. Set集合类型简介.二. 增加元素相关命令2.1 向集合中添加元素(sadd)2.2 从集合中移动元素( smove ) 三. 查询元素相关操作.3.1 查询集合中存在的所有元素.( smembers )3.2 查询集合中是否存在member( sismember ) 四. 随机获取集合中的元素4.1 随机获取集合中的n个元素…

LabVIEW中的非阻塞定时器

在LabVIEW编程中&#xff0c;通常需要在某些任务执行过程中进行非阻塞的延时操作。例如&#xff0c;显示某条信息一段时间&#xff0c;同时继续执行其他任务&#xff0c;并在延时时间结束后停止显示该信息。这类需求通常用于处理优先级不同的信息显示&#xff0c;如错误信息需要…

2024双十一买啥最划算?四款必入的数码好物推荐!

随着2024年双十一购物狂欢节的临近&#xff0c;各大电商平台纷纷推出了一系列令人期待的优惠活动&#xff0c;这无疑是一年中最佳的采购时机。对于追求科技潮流与实用主义的消费者而言&#xff0c;选择在这个时候入手心仪已久的数码产品无疑是明智之举。为了帮助大家抓住这波促…

Windows系统操作技巧

文章目录 I 打开‌任务管理器II Windows的run功能常用命令RDP协议的远程连接I 打开‌任务管理器 ‌通过快捷键打开‌任务管理器 ‌[Ctrl + Shift + Esc]:这是最常用的方法,直接按下这三个键即可快速打开任务管理器。‌Ctrl + Alt + Delete‌:按下这三个键后会弹出一个菜单,…

PostgreSQL数据库定期清理归档(pg_wal)日志

一、配置归档模式 在postgresql.conf文件中设置archive_mode on来启用归档功能。 二、设置归档命令 同样在postgresql.conf中&#xff0c;设置archive_command参数&#xff0c;指定一个shell命令来处理归档日志&#xff0c;例如&#xff1a; archive_command cp %p /home/…

中英文在线翻译工具大盘点

中英文在线翻译工具如同语言世界的桥梁&#xff0c;连接着两种不同的文化和语言体系。接下来&#xff0c;让我们一同走进这个精彩纷呈的中英文在线翻译工具集锦&#xff0c;探寻它们的奥秘与魅力。 1.福昕在线翻译 链接直达>>https://fanyi.pdf365.cn/doc 这款在线翻…

Git的基本使用入门

参考&#xff1a;Git速查 git的基本概念 git常用命令大部分是基于三大分区来执行的。先来了解一些专有名词吧。 工作区&#xff0c;也叫 Working Directory暂存区&#xff0c;也叫 stage&#xff0c;index版本库&#xff0c;也叫本地仓库&#xff0c;commit History 将代码推…

书店系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;图书管理&#xff0c;论坛信息管理&#xff0c;用户管理&#xff0c;公告信息管理&#xff0c;基础数据管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;论坛信息&…