算法训练营day46|动态规划 part08:完全背包 (LeetCode 139. 单词拆分、多重背包理论基础)

文章目录

  • 139. 单词拆分 (求排列方法)
    • 回溯思路分析
    • 背包思路分析
    • 代码实现
    • 思考总结
  • 多重背包理论基础

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

回溯思路分析

之前讲解回溯法专题的时候,讲过的一道题目回溯算法:分割回文串,就是枚举字符串的所有分割情况。
本道是枚举分割所有字符串,判断是否在字典里出现过。
回溯法C++代码:(会超时)

class Solution {
private:bool backtracking (const string& s, const unordered_set<string>& wordSet, int startIndex) {if (startIndex >= s.size()) {return true;}for (int i = startIndex; i < s.size(); i++) {string word = s.substr(startIndex, i - startIndex + 1);if (wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, i + 1)) {return true;}}return false;}
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());return backtracking(s, wordSet, 0);}
};

递归的过程中有很多重复计算,可以使用数组保存一下递归过程中计算的结果。

这个叫做记忆化递归,这种方法我们之前已经提过很多次了。

使用memory数组保存每次计算的以startIndex起始的计算结果,如果memory[startIndex]里已经被赋值了,直接用memory[startIndex]的结果。

C++代码如下:

class Solution {
private:bool backtracking (const string& s,const unordered_set<string>& wordSet,vector<bool>& memory,int startIndex) {if (startIndex >= s.size()) {return true;}// 如果memory[startIndex]不是初始值了,直接使用memory[startIndex]的结果if (!memory[startIndex]) return memory[startIndex];for (int i = startIndex; i < s.size(); i++) {string word = s.substr(startIndex, i - startIndex + 1);if (wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, memory, i + 1)) {return true;}}memory[startIndex] = false; // 记录以startIndex开始的子串是不可以被拆分的return false;}
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> memory(s.size(), 1); // -1 表示初始化状态return backtracking(s, wordSet, memory, 0);}
};

背包思路分析

单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。

拆分时可以重复使用字典中的单词,说明就是一个完全背包!

动规五部曲分析如下:

  1. 确定dp数组以及下标的含义

dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。

  1. 确定递推公式

如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。

所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。

  1. dp数组如何初始化

从递推公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递推的根基,dp[0]一定要为true,否则递推下去后面都都是false了。

那么dp[0]有没有意义呢?

dp[0]表示如果字符串为空的话,说明出现在字典里。

但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。

下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。

  1. 确定遍历顺序

题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。

还要讨论两层for循环的前后顺序。

如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品

而本题其实我们求的是排列数,为什么呢。 拿 s = “applepenapple”, wordDict = [“apple”, “pen”] 举例。

“apple”, “pen” 是物品,那么我们要求 物品的组合一定是 “apple” + “pen” + “apple” 才能组成 “applepenapple”。

“apple” + “apple” + “pen” 或者 “pen” + “apple” + “apple” 是不可以的,那么我们就是强调物品之间顺序。

所以说,本题一定是 先遍历 背包,再遍历物品。

如果先遍历物品再遍历背包

class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> dp(s.size() + 1, false);dp[0] = true;for (int j = 0; j < wordDict.size(); j++) { // 物品for (int i = wordDict[j].size(); i <= s.size(); i++) { // 背包string word = s.substr(i - wordDict[j].size(), wordDict[j].size());// cout << word << endl;if ( word == wordDict[j] && dp[i - wordDict[j].size()]) {dp[i] = true;}// for (int k = 0; k <= s.size(); k++) cout << dp[k] << " "; //这里打印 dp数组的情况 // cout << endl;}}return dp[s.size()];}
};

使用用例:s = “applepenapple”, wordDict = [“apple”, “pen”],对应的dp数组状态如下:
在这里插入图片描述
最后dp[s.size()] = 0 即 dp[13] = 0 ,而不是1,因为先用 “apple” 去遍历的时候,dp[8]并没有被赋值为1 (还没用"pen"),所以 dp[13]也不能变成1。

除非是先用 “apple” 遍历一遍,再用 “pen” 遍历,此时 dp[8]已经是1,最后再用 “apple” 去遍历,dp[13]才能是1。

  1. 举例推导dp[i]
    在这里插入图片描述

代码实现

class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {set<string> dict(wordDict.begin(),wordDict.end());cout<<endl;vector<bool> dp(s.size()+1,false);dp[0]=true;for(int j=0;j<=s.size();j++){   // 遍历背包for(int i=0;i<j;i++){       // 遍历物品if(dict.find(s.substr(i,j-i))!=dict.end()&&dp[i]!=false){//substr(起始位置,截取的个数)dp[j]=true;}}}return dp[s.size()];}
};

思考总结

再理解一下这里为什么是排列不是组合。


多重背包理论基础

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

多重背包和01背包是非常像的, 为什么和01背包像呢?

每件物品最多有Mi件可用,把Mi件摊开,其实就是一个01背包问题了。

两种实现方法:

void test_multi_pack() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};vector<int> nums = {2, 3, 2};int bagWeight = 10;for (int i = 0; i < nums.size(); i++) {while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开weight.push_back(weight[i]);value.push_back(value[i]);nums[i]--;}}vector<int> dp(bagWeight + 1, 0);for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}for (int j = 0; j <= bagWeight; j++) {cout << dp[j] << " ";}cout << endl;}cout << dp[bagWeight] << endl;}
int main() {test_multi_pack();
}

也有另一种实现方式,就是把每种商品遍历的个数放在01背包里面在遍历一遍。

void test_multi_pack() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};vector<int> nums = {2, 3, 2};int bagWeight = 10;vector<int> dp(bagWeight + 1, 0);for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量// 以上为01背包,然后加一个遍历个数for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);}}// 打印一下dp数组for (int j = 0; j <= bagWeight; j++) {cout << dp[j] << " ";}cout << endl;}cout << dp[bagWeight] << endl;
}
int main() {test_multi_pack();
}

看出是01背包里面在加一个for循环遍历一个每种商品的数量。 和01背包还是如出一辙的。

多重背包在面试中基本不会出现,力扣上也没有对应的题目,大家对多重背包的掌握程度知道它是一种01背包,并能在01背包的基础上写出对应代码就可以了

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TorchDynamo初探②:Torch.FX调研和实践

作者&#xff5c;strint 1 概要 torch.fx 是 PyTorch 官方发布的 Python 到 Python 的代码变换工具。如果你想做 Torch 代码变换&#xff0c;torch.fx 是首选工具。 torch.fx 会将 Torch 代码 trace 成 6 种基础的 node 组成的 graph&#xff0c;基于这个 graph 可以方便的做各…

动态库的制作与使用及 动态库加载失败解决

加载动态库时有时会出现error while loading shared libraries&#xff1a;libcalc.so:可以通过lld命令查看动态库的依赖关系&#xff0c;发现libcalc.so时not found 原因 查找的优先级是DT_RPATH->LD_LIBRARY_PATH->/etc/ld.so.cache->/lib/,/usr/lib 找不到一个优…

RouterOS-配置PPPoEv4v6 Server

1 接口 ether3 出接口 ether4 内网接口 2 出接口 出接口采用PPPoE拨号SLAAC获取前缀&#xff0c;手动配置后缀 2.1 选择出接口interface&#xff0c;配置PPPoE client模式 2.2 配置PPPoE client用户名和密码 2.3 从PPPoE client获取前缀地址池 2.4 给出接口选择前缀并配置…

Hystrix和Sentinel熔断降级设计理念

目录 1 基本介绍2 Hystrix信号量和线程池区别2.1 信号量模式2.2 线程池模式2.3 注意 3 Sentinel介绍 1 基本介绍 Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现&#xff0c;例如请求响应时间长或异常比例升高的时候&#xff0c;则对这个资源…

[Spring] @Configuration注解原理

文章目录 1.Configuration注解介绍1.1 容器注入ConfigurationClassPostProcessor后置处理器1.2 ConfigurationClassPostProcessor介绍 2.ConfigurationClassPostProcessor解析2.1 Parse12.2 Parse22.3 Parse32.4 Parse42.5 Parse5 3.ConfigurationClassParser解析4.Configurati…

Spring系列文章:Spring6集成MyBatis3.5

1、引入依赖 <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><dependency><groupId>org.mybatis</groupId><artif…

SAP中的新旧事务码

SAP中的新旧事务码 SAP随着新版本的发布&#xff0c;我们知道sap已经更新了很多的程序和TCODE。sap提供了很多新的TCODE来替换旧的TCODE&#xff0c;新TCODE有很多的新特性和新功能。在这个这种情况下&#xff0c;很多旧TCODE就会被废弃。我们如何查找这个替换呢&#xff1f; …

KMP算法

个人理解 我理解的KMP 算法就是记录前缀与后缀&#xff0c;每当遇到不匹配的时候由于后缀已经被匹配过&#xff0c;所以下次应该跳到匹配过的后缀也就是相应的前缀后面在进行匹配。 如何计算前缀 参考卡哥网站 前缀计算 然后利用前缀表去做匹配 leetcode 28 class Solutio…

【Kubernetes理论篇】2023年最新CKA考题+解析

文章目录 第一题&#xff1a;RBAC授权访问控制第二题&#xff1a;Node节点维护第三题&#xff1a;K8S集群版本升级第四题&#xff1a;ETCD数据库备份恢复第五题&#xff1a;NetworkPolicy网络策略第六题&#xff1a;Service四层负载第七题&#xff1a;Ingress七层负载第八题&am…

nodejs采集淘宝、天猫网商品详情数据以及解决_m_h5_tk令牌及sign签名验证(2023-09-09)

一、淘宝、天猫sign加密算法 淘宝、天猫对于h5的访问采用了和APP客户端不同的方式&#xff0c;由于在h5的js代码中保存appsercret具有较高的风险&#xff0c;mtop采用了随机分配令牌的方式&#xff0c;为每个访问端分配一个token&#xff0c;保存在用户的cookie中&#xff0c;通…

OpenCV 11(图像金字塔)

一、 图像金字塔 **图像金字塔**是图像中多尺度表达的一种&#xff0c;最主要用于图像的分割&#xff0c;是一种以多分辨率来解释图像的有效但概念简单的结构。简单来说, 图像金字塔是同一图像不同分辨率的子图集合. 图像金字塔最初用于机器视觉和图像压缩。其通过梯次向下采…

【面试心得】WebBench 整理

在面试九识的时候&#xff0c;被问到了WebBench的原理&#xff0c;当时没答上来&#xff0c;这里做一个整理 WebBench 源码【带注释】&#xff1a;GitHub - YukunJ/annotated-webbench-1.5: bilingually annotated Webbench-1.5 webbench是一个轻量的压测工具&#xff0c;可以…

docker desktop如何一键进入容器内部

对着对应的容器 点击 view files

交友盲盒完整版——详细源码分享

现在目前比较火热的一款app交友盲盒是通过uniappspringboot技术来制作的&#xff0c;原理其实很简单&#xff0c;大家一看便知。 大家自行下载到手机里面去使用即可&#xff0c;不支持ios手机 演示地址&#xff1a;https://share.weiyun.com/l3ovztce 下面就是给大家分享源码了…

快速文件复制与删除工具,将复制时文件夹里的原文件删除掉

无论是工作还是生活&#xff0c;我们都离不开文件的复制和管理。然而&#xff0c;手动复制文件不仅费时费力&#xff0c;而且容易出错。现在&#xff0c;我们为您推荐一款快速文件复制与删除工具&#xff0c;让您的文件管理更加高效&#xff01; 首先&#xff0c;我们要进入文…

由于电脑出现msvcr110.dll提示错误的解决方法

最近&#xff0c;我在尝试运行一款新的软件时&#xff0c;突然遇到了一个错误提示&#xff0c;提示说缺少msvcr110.dll文件&#xff0c;导致软件无法启动。在使用电脑过程中&#xff0c;我们常常会遇到一些系统文件丢失的问题。其中&#xff0c;msvcr110.dll是Windows操作系统中…

Layui快速入门之第二节布局容器(固定宽度与完整宽度)

目录 一&#xff1a;固定宽度 二&#xff1a; 完整宽度 一&#xff1a;固定宽度 将栅格放入一个带有 class"layui-container" 的特定容器中&#xff0c;以便在小屏幕以上的设备中固定宽度&#xff0c;让列可控(两侧有留白效果) <!--固定宽度(两侧有留白效果)--&…

el-dialog弹窗中进度条的(mqtt提供)数据无法清空(清空方法)

清空方法 this.$nextTick(()>{this.$refs.devicefromDialog.clearValidate(airSwitchNo);//清除的校验规则prop传的值this.$refs[devicefromDialog].resetFields();//清除表单内容}) 场景&#xff1a;进度条的数据需要在关闭的时候&#xff0c;清空上一次的缓存记录&#xf…

Java开发之Redis核心内容【面试篇 完结版】

文章目录 前言一、redis使用场景1. 知识分布2. 缓存穿透① 问题引入② 举例说明③ 解决方案④ 实战面试 3. 缓存击穿① 问题引入② 举例说明③ 解决方案④ 实战面试 4. 缓存雪崩① 问题引入② 举例说明③ 解决方案④ 实战面试 5. 缓存-双写一致性① 问题引入② 举例说明③ 解决…

动态规划之子数组系列

子数组系列 1. 环形⼦数组的最⼤和2. 乘积最大子数组3. 等差数列划分4. 最长湍流子数组5. 单词拆分6. 环绕字符串中唯⼀的子字符串 1. 环形⼦数组的最⼤和 1.题目链接&#xff1a;环形⼦数组的最⼤和 2.题目描述&#xff1a;给定一个长度为 n 的环形整数数组 nums &#xff0c…