DAY48:动态规划(十二)完全平方数(类似零钱兑换)+单词拆分(注意背包思路!)

news2024/11/28 21:00:16

文章目录

    • 279.完全平方数(类似零钱兑换)
      • 思路
        • DP数组含义
        • 递推公式
        • 初始化
        • 遍历顺序
      • 最开始的写法:有1个用例没过
      • 修改完整版
      • 总结
    • 139.单词拆分(递推公式注意)
      • 思路1:遍历单词分割点
        • DP数组含义
        • 递推公式
        • 初始化
        • 遍历顺序
      • 思路1完整版
        • debug测试:解答错误,原因是substr参数错误
      • 思路2:完全背包(遍历顺序注意)
        • 递推公式
        • 遍历顺序
      • 思路2完整版
      • 两种思路时间复杂度对比
        • 优缺点:
      • 总结

279.完全平方数(类似零钱兑换)

  • 本题也是求装满背包的最小物品个数,和零钱兑换一样。涉及到初始值的问题。
  • 本题注意思路,物品自带限制的情况这个限制可以加到递推公式里

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

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

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 10^4

思路

本题也是给出一个目标值,要求凑成目标值所需的最小物品个数。由示例可知,物品可以重复使用。

因此本题属于完全背包问题。n就是背包容量,完全平方数就是物品。物品个数的上限实际上就是n

DP数组含义

dp[j]表示:装满容量为j的背包,最少需要dp[j]个完全平方数。

递推公式

递推公式同上一题零钱兑换

dp[j]=min(dp[j],dp[j-i*i]+1);//物品直接用i*i来表示

初始化

本题的初始化因为是最小值,所以也是全部初始化为INT_MAX,再令dp[0]=0

dp[0]=0这个初始化非常重要,所有的递推都是从0开始,相当于最开始只有j=coins[i]的时候,才能更新dp[j]的数值

遍历顺序

本题求最小物品个数,和方案数目无关,组合or排列都不影响物品的个数,因此都可以。

最开始的写法:有1个用例没过

class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=0;i<n;i++){
            for(int j=i*i;j<=n;j++){//j在里层可以直接合并边界条件
                if(dp[j-i*i]==INT_MAX) continue;//为了保证背包装满,装不满的情况直接continue 
                dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
		if(dp[n]==INT_MAX) return -1;
        return dp[n];
    }
};

在这里插入图片描述

修改完整版

针对这个用例的情况,只需要修改for循环的起始值和终止,令其包括j=1,也就是n=1情况即可

class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;
        //修改了起始值,把j=i*i也就是n=1的情况包括了
        for(int i=1;i<=n;i++){
            for(int j=i*i;j<=n;j++){//j在里层可以直接合并边界条件
                if(dp[j-i*i]==INT_MAX) continue;//为了保证背包装满,装不满的情况直接continue 
                dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
		if(dp[n]==INT_MAX) return -1;
        return dp[n];
    }
};
  • 时间复杂度: O(n * √n)
  • 空间复杂度: O(n)

或者写成

  • 完全平方数的情况,物品循环应该是i*i<=n,这样的话i从0开始也可以
class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=0;i*i<=n;i++){
            for(int j=i*i;j<=n;j++){//j在里层可以直接合并边界条件
                if(dp[j-i*i]==INT_MAX) continue;//为了保证背包装满,装不满的情况直接continue 
                dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
		if(dp[n]==INT_MAX) return -1;
        return dp[n];
    }
};
  • 先遍历背包再遍历物品的写法
class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=0;i*i<=n;i++){
            for(int j=i*i;j<=n;j++){//j在里层可以直接合并边界条件
                if(dp[j-i*i]==INT_MAX) continue;//为了保证背包装满,装不满的情况直接continue 
                dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
		if(dp[n]==INT_MAX) return -1;
        return dp[n];
    }
};

总结

本题的重要注意点就是,不要局限于判断数字(物品)是不是完全平方数,物品本身的限制条件并不复杂,完全可以直接在递推公式里面进行替换,也就是把递推公式换成dp[j]=min(dp[j],dp[j-i*i]+1)

139.单词拆分(递推公式注意)

  • 本题的核心,就是继承上一个递推的状态

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet""code" 拼接成。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
     注意,你可以重复使用字典中的单词。

示例 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 和 wordDict[i] 仅有小写英文字母组成
  • wordDict 中的所有字符串 互不相同

思路1:遍历单词分割点

因为本题并不是要求给出所有的拼接结果,只是判断能不能进行拼接,因此可以用背包问题进行解决。

目标字符串就是背包,字符串列表就是物品列表。

大致思路就是按照长度挨个遍历目标字符串遍历到j的时候,判断当前长度的字符串,能不能在字典里找到

DP数组含义

dp[j]表示,长度为j的字符串,能不能被字典中的单词组成

字典中的单词可以重复使用,且**dp[j]遍历的一定是原字符串**,为了防止"apple"“pen”"apple"出现乱序的问题

递推公式

遍历到j的时候,主要考虑的dp[j]是由dp[i]dp[j-i]的状态推出来的,i是枚举0–j中的分割点

也就是说,我们需要枚举[0,j)中的分割点i看[0,i-1]和[i,j-1]这两个子串,是不是都符合能在字典中找到的要求。

如果两个字符串都合法,那么最后的结果也合法。

也就是说,递推公式为:

dp[j]=dp[i]&&dp[j-i];//i是枚举的[0,j-1]所有分割点

单词拆分官方题解:单词拆分 - 单词拆分 - 力扣(LeetCode)

初始化

全部初始化为false,只有dp[0]初始化为true

遍历顺序

因为i是j枚举过程中的子数组分割点,因此循环的嵌套是j在外,i在内

思路1完整版

  • 对于检查一个字符串是否出现在给定的字符串列表里,一般可以考虑哈希表来快速判断,因为哈希表带有.find()函数而数组没有
  • 枚举所有可能的分割点,然后对原字典中的单词进行查找和对比
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //先构造哈希表把word字典放进去,方便find查找
        unordered_set<string>wordD;
        for(auto word:wordDict){
            wordD.insert(word);//word这里就是数组元素本身
        }
        vector<bool>dp(s.size()+1,false);
        dp[0]=true;
        for(int j=0;j<=s.size();j++){
            //内层是枚举[0,j)以来的所有分割点
            for(int i=0;i<j;i++){
                //先看dp[j-i]能不能放进去
                auto res = s.substr(i,j-i);//注意substr的参数
                if(wordD.find(res)!=wordD.end()&&dp[i]==true){//如果找到了res
                    dp[j]=true;//再判断dp[j]是不是true
                } 
            }
        }
        return dp[s.size()];

    }
};

debug测试:解答错误,原因是substr参数错误

在这里插入图片描述
最开始的写法是:

auto res = s.substr(j-i,j);//参数错误,是起点+长度

但是实际上substr这么用是错误的!substr函数的两个参数分别是起始位置和子串的长度,而不是结束位置。所以应该是

auto res = s.substr(i,j-i);//一定要注意两个参数是起点+长度

思路2:完全背包(遍历顺序注意)

上面这种思路实际上不算是完全背包了,因为和字典里面的物品已经没啥关系了。

实际上这道题也可以用完全背包方法做,也就是遍历物品。遍历物品的大致思路是,对于每一个背包容量(字符串长度)[0--j],都进行是否能被拼成的判断,即遍历所有的单词,并判断当前长度的字符串末尾是否是这个单词

DP数组含义和思路1一样,动态规划都是求什么,DP数组的含义就是什么

dp[j]依旧代表长度为j的字符串,能不能被字符数组中的元素组成

递推公式

递推公式也属于先判断dp[i-len]是不是符合要求,符合要求才返回true的类型

if(dp[i - len]==true)
    dp[i] = true;

遍历顺序

本题实际上属于排列问题,因为将字典数组内的元素看作物品的话,物品"apple"“pen”“apple"填满背包,和"pen”"apple"apple"填满背包是不一样的!因此这是一个排列问题,必须背包在外,物品在内。

思路2完整版

  • 第一种写法,直接对比s字符串的末尾的符合每个单词长度的元素,去对比是不是能在字典里找到
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //dp数组和思路1相同
        vector<bool> dp(s.size()+1,false);
        //dp[0]初始化
        dp[0] = true;
        //背包在外物品在内
        for(int i=0;i<=s.size();i++){
            //物品就是字典里的元素
            for(int j=0;j<wordDict.size();j++){
                int len=wordDict[j].size();
                if(i>=len){
                    auto res = s.substr(i-len,len);
                    //只要末尾元素和当前元素相同,且前面的部分也是true,就返回true
                    if(res==wordDict[j]&&dp[i-len]==true) dp[i]=true;
                }
            }
        }
        return dp[s.size()];
    }
};
  • 或者另一种做法,建立哈希表,也是找末尾元素,看字符串符合单词长度的末尾元素能不能在字典里找到,这里用了find()用法,更好理解一些
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //直接把string数组的内容复制到set中
        unordered_set<string> wordD(wordDict.begin(), wordDict.end());
        //dp数组和思路1相同
        vector<bool> dp(s.size()+1,false);
        //dp[0]初始化
        dp[0] = true;
        //背包在外物品在内
        for(int i=0;i<=s.size();i++){
            //物品就是字典里的元素
            for(int j=0;j<wordDict.size();j++){
                int len=wordDict[j].size();
                if(i>=len){
                    auto res = s.substr(i-len,len);
                    //只要末尾这一段元素能和字典单词对应,且前面的部分也是true,就返回true
                    if(wordD.find(res)!=wordD.end()&&dp[i-len]==true) dp[i]=true;
                }
            }
        }
        return dp[s.size()];
    }
};

两种思路时间复杂度对比

思路一:

  • 时间复杂度:O(n^2 * m),其中n是字符串s的长度,m是wordDict中最大字符串的长度。这是因为我们需要检查所有的子字符串,其复杂度为O(n^2),并且对每个子字符串我们都需要在字典中进行查找,其复杂度为O(m)
  • 空间复杂度:O(n + k),其中n是字符串s的长度,k是wordDict的大小。空间复杂度主要是dp数组和字典的大小。

思路二:

  • 时间复杂度:O(n * l * m),其中n是字符串s的长度,l是字典的大小,m是字典中最大字符串的长度。对于每个位置,我们都需要检查所有的单词是否可以作为前缀,所以时间复杂度是O(n * l * m)
  • 空间复杂度:O(n + k),其中n是字符串s的长度,k是wordDict的大小。空间复杂度主要是dp数组和字典的大小。

优缺点:

思路一:

  • 优点:一的优点在于其相对直观,通过检查所有可能的子串和字典中的单词进行比较
  • 缺点:如果字典中的单词长度非常大,那么时间复杂度将会非常高。

思路二:

  • 优点:二遍历字典的方式会比方法一更有效,因为我们直接跳过了那些长度大于当前检查子串长度的单词,这降低了不必要的计算
  • 缺点:如果字典的大小非常大,那么时间复杂度将会非常高。

总结

这道题目的关键在于正确理解dp数组的含义。在这个代码中,dp[j]表示字符串s的前j个字符是否可以用wordDict中的词语拆分

完全背包的做法是建立哈希表,找挨个增加长度的过程中,字符串的末尾元素。看字符串里符合单词长度的末尾元素能不能在字典里找到,能找到则[i-j]这一段为true,如果dp[i-j]也是true,那么dp[i]就是true。(i是字符串长度)

遍历单词分割点的做法是,我们枚举所有可能的分割点j,如果找到一个有效的分割点(即字符串s的子串在字典中且dp[j]为true),就更新dp[i]为true。

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

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

相关文章

基于Java+SpringBoot+Vue前后端分离旅游网站详细设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

导航菜单 改变背景色

直接参考官网上的案例即可 //active-text-color 点击时修改字体颜色 // background-color 背景色 // text-color 字体颜色<el-menudefault-active"2"class"el-menu-vertical-demo"open"handleOpen"close"handleClose"background…

Java:控制流程 + 数组 详解(原理 + 用法 + 例子)

目录 控制流程块作用域if 条件语句for while 循环switch 多重选择break continue 中断控制流程语句 大数值数组多维数组字符串类型数组Array.sort() 数组排序for each 循环 控制流程 块作用域 块&#xff08;即复合语句&#xff09;是指由一对大括号{}括起来的若干条简单的 Ja…

ARP解析MAC地址的全过程(ARP的工作机制)

目录 ARP解析MAC地址的过程&#xff1a; 源码等资料获取方法 以太网环境下&#xff0c;同一个网段的主机之间需要互相知道对方的MAC地址&#xff0c;才能访问。 TCP/IP协议栈从上层到下层的封装过程中&#xff0c;第三层封装需要知道目的IP&#xff0c;第二层封装需要知道目…

Linux下安装Mysql (CentOS 7) 详解

文章目录 前言环境检查查看是否安装MySql查看系统版本 源安装安装mysql的yum源官网下载从windows上传到linuxrz命令 方法2&#xff1a; 安装Mysql常见错误密钥问题安装后查看mysql是否可以工作查看是否安装成功启动服务 登录mysql配置文件方法&#xff08;免密码&#xff09; 使…

linux 安装 cuda

需求&#xff1a; inux 下安装 cuda 进程&#xff1a; 先查看一下系统版本 uname -a查看能支持什么版本的cudacuda toolkit 下载 wget https://developer.download.nvidia.com/compute/cuda/11.1.0/local_installers/cuda_11.1.0_455.23.05_linux.run sudo sh cuda_11.1.0_4…

MySql冷门但是很有用的语句

目录 1 查看当前的所有执行的进程 查看简略信息 查看详细信息 2 在所有数据库中查询包含某个字段的表 精确 模糊 1 查看当前的所有执行的进程 查看简略信息 show processlist 查看详细信息 show full processlist 终止进程 kill id 2 在所有数据库中查询包含某个字段…

gurobi安装vs配置gurobi

gurobi安装&vs配置gurobi 1、注册账号并登录 2、下载gurobi optimizer 3、获取license:User Portal (gurobi.com) online course可以免ip验证。 4、GENERATE NOW会生成&#xff0c;打开cmd进入gurobi安装路径&#xff08;如F:\gurobi1001\win64\bin>&#xff09;&am…

分布式事务 Seata

分布式事务 Seata 事务介绍分布式理论Seata 介绍Seata 部署与集成Seata TC Server 部署微服务集成 Seata XA 模式AT 模式AT 模式执行过程读写隔离写隔离读隔离 实现 AT 模式 TCC 模式TCC 模式介绍实现 TCC 模式 Saga 模式Seata 四种模式对比 事务介绍 事务&#xff08;Transac…

分布式光伏监控系统运维系统实时查看数据分布式光伏电站监控管理

光伏电站是一种利用太阳能发电的设施&#xff0c;随着人们对可再生能源的需求不断增加&#xff0c;光伏电站的建设也越来越普遍。但是&#xff0c;光伏电站的运营和管理需要高质量的监控系统来确保其正常运行。本文将介绍光伏电站监控系统的组成及其原理。 详细软件具体需求可…

php连接上mysql数据库该的配置方法

用mysql官方的管理工具workbench&#xff1a; 打开导出界面后&#xff0c;下一步&#xff0c;选择csv格式&#xff0c;导出后excel就能打开了 如果你需要在程序代码中导出&#xff0c;需要找到对应代码的excel处理库。 如php 的 phpExcel( 最新版已更名为 phpoffice/phpspread…

vue3组件中使用live2d看板娘(官方包形式)

文章目录 先看最终效果吧关于官方包下载使用 vue3中调整使用基础使用关于样式调整 vue中Html主页调试&#xff08;备用调试方案&#xff09; 先看最终效果吧 看着还可以&#xff0c;其实还有很多问题没解决&#xff0c;因为是完全靠js渲染&#xff0c;实际上这个live2d的canvas…

前端开发多人协作的团队项目时应该要配置的一些规则

本文主要记录了团队开发一个前端项目需要进行的一些前期配置&#xff0c;例如Eslint语法检验&#xff0c;prettierrc格式化规则&#xff0c;以及提交代码时的规则等等。 目录 1.搭建项目 2.Eslint配置&#xff08;代码检验工具&#xff09; 2.1安装Eslint 2.2配置Eslint 2…

【Ajax】笔记-取消请求

在进行AJAX(Asynchronous JavaScript and XML) 请求时&#xff0c;有时候我们需要取消正在进行的请求。取消请求可以帮助我们提高用户体验&#xff0c;病减少不必要的网络流量和服务器负载。 取消请求的方法 在AJAX请求中&#xff0c;我们可以使用以下方法来取消正在进行的请求…

【大虾送书第三期】进阶高级Python开发工程师,不得不掌握的Python高并发编程

目录 ✨写在前面 ✨主要内容 ✨本书特色 ✨关于作者 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;免费送书活动专栏地址 写在前面 Python成为时下技术革新的弄潮儿&#xff0c;全民Python的发展趋势让人们不再满足于简单地运行Python…

MacBook Java开发环境搭建记录

一、Homebrew的镜像设置 对于Java JDK的安装&#xff0c;我们更推荐使用Homebrew来进行安装管理。但Homebrew的curl国外源的下载速度实在是一言难尽&#xff0c;时常还会发生无法访问的情况。 那么我们此时的解决方法就有两种了&#xff0c;第一种便是使用全局的VPN代理进行下载…

SQLite Expert Personal的简单使用

官方网站&#xff1a; sqliteexpert官方网址 - SQLite administration | SQLite Expert ​—————————————————————————————————————————— 免费的受欢迎的 SQLite 管理工具 SQLite Manager 0.8.0 Firefox Plugin 这是一个 Firefox …

【MySQL】多表查询(四)

&#x1f697;MySQL学习第四站~ &#x1f6a9;本文已收录至专栏&#xff1a;MySQL通关路 ❤️文末附全文思维导图&#xff0c;感谢各位点赞收藏支持~ 之前我们介绍DQL语句&#xff0c;也就是数据查询语句的时候&#xff0c;介绍的查询操作都是单表查询&#xff0c;他的功能当然…

山西电力市场日前价格预测【2023-07-20】

日前价格预测 预测明日&#xff08;2023-07-20&#xff09;山西电力市场全天平均日前电价为337.62元/MWh。其中&#xff0c;最高日前电价为375.88元/MWh&#xff0c;预计出现在06: 00。最低日前电价为291.47元/MWh&#xff0c;预计出现在13: 30。 价差方向预测 1&#xff1a;实…

Elasticsearch 介绍及java集成

一、Elasticsearch 基础介绍 ElasticSearch 是分布式实时搜索、实时分析、实时存储引擎&#xff0c;简称&#xff08;ES)&#xff0c; 成立于2012年&#xff0c;是一家来自荷兰的、开源的大数据搜索、分析服务提供商&#xff0c;为企业提供实时搜索、数据分析服务&#xff0c;…