算法训练营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.hqwc.cn/news/107730.html

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

相关文章

docker 方式安装mysql 主从方式keepalived实现高可用

一、环境介绍 二、MySQL安装 在两台服务器上都安装mysql 1、拉取镜像 docker pull mysql:8.0.272、创建挂载目录 mkdir -p /data/mysql/3、运行容器 主节点 docker run \--restartalways \--name master_mysql -p 3306:3306 \-e MYSQL_ROOT_PASSWORD123456 -d \-v /data/m…

用户权限数据转换为用户组列表(3/3) - Excel PY公式

最近Excel圈里的大事情就是微软把PY塞进了Excel单元格&#xff0c;可以作为公式使用&#xff0c;轻松用PY做数据分析。系好安全带&#xff0c;老司机带你玩一把。 实例需求&#xff1a;如下是AD用户的列表,每个用户拥有该应用程序的只读或读写权限&#xff0c;现在需要创建新的…

SpringMVC笔记

文章目录 一、SpringMVC简介1、什么是MVC2、什么是SpringMVC3、SpringMVC的特点 二、HelloWorld1、开发环境2、创建maven工程a>添加web模块b>打包方式&#xff1a;warc>引入依赖 3、配置web.xmla>默认配置方式b>扩展配置方式 4、创建请求控制器5、创建springMVC…

面试:如何实现分布式锁?看清楚,不是实现分布式事务!!

面试复盘&#xff1a;如何实现分布式锁&#xff1f; 目录 面试复盘&#xff1a;如何实现分布式锁&#xff1f; 1.分布式锁要求 2.实现方案 3.数据库分布式锁 3.1 悲观锁 3.2 乐观锁 4.Zookeeper 分布式锁 4.1 引入 Curator 和 ZooKeeper 4.2 配置 ZooKeeper 连接 4.3 编…

【送书活动】畅销书《Kali Linux高级渗透测试》更新版速速查收~

文章目录 每日一句正能量前言本书概况读者对象赠书活动目录 每日一句正能量 其实&#xff0c;人生很多东西无所谓最好的&#xff0c;只要你认为值得就是最好。 前言 对于企业网络安全建设工作的质量保障&#xff0c;业界普遍遵循PDCA&#xff08;计划&#xff08;Plan&#xf…

SAP HANA 体系结构,LandScape,规模调整:完整教程

目录 一、SAP HANA 体系结构 二、SAP HANA 景观 三、SAP HANA 大小调整 SAP HANA 数据库是以主内存为中心的数据管理平台。 SAP HANA 数据库在 SUSE Linux Enterprises Server 上运行&#xff0c;并基于 C 语言构建。 SAP HANA 数据库可以分发到多台计算机。 SAP HANA 的优…

SpringBoot-可视化监控

一、添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--采集应用的指标信息&#xff0c;prometheus--> <dependency…

docker容器技术实战-2

03docker hub 首先注册上号&#xff1a; https://hub.docker.com/ 上传自己的镜像仓库 创建自己的仓库 webserver 拉取镜像 配置加速器 04搭建私有仓库 上传镜像 在主机1上 在主机2 上 激活内核选项 激活内核选项文件传输过去 配置使用非加密端口 05 docker私有仓库 仓库加…

界面控件DevExpress WPF TreeMap,轻松可视化复杂的分层结构数据!

DevExpress WPF TreeMap控件允许用户使用嵌套的矩形块可视化复杂的平面或分层结构数据。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&a…

fabic如何将绘图原点移到画布中心

情况说明&#xff1a; fabic默认绘图原点为left&#xff1a;0&#xff0c;top&#xff1a;0 后端给我的内容是按照x&#xff0c;y返回的&#xff0c;需要将坐标系移到fabic画布的中心位置&#xff0c;找了下网上合适的砖&#xff0c;想一句命令直接设置&#xff0c;结果没有。…

27.EI文章复现《高比例清洁能源接入下计及需求响应的配电网重构》

下载地址&#xff1a;高比例清洁能源接入下计及需求响应的配电网重构 1主要内容 该程序复现《高比例清洁能源接入下计及需求响应的配电网重构》&#xff0c;以考虑网损成本、弃风弃光成本和开关操作惩罚成本的综合成本最小为目标&#xff0c;针对配电网重构模型的非凸性&…

告别HR管理繁琐,免费低代码平台来帮忙

编者按&#xff1a;本文着重介绍了使用免费且高效的低代码平台实现的HR管理系统在一般日常人力资源管理工作中的关键作用。 关键词&#xff1a;低代码平台、HR管理系统 1.HR管理系统有什么作用&#xff1f; HR管理系统作为一款数字化工具&#xff0c;可为企业提供全方位的人力资…