代码随想录 | Day38 | 动态规划 :01背包应用 目标和一和零

代码随想录 | Day38 | 动态规划 :01背包应用 目标和&&一和零

动态规划应该如何学习?-CSDN博客

01背包模板 | 学习总结-CSDN博客

难点:

代码都不难写,如何想到01背包并把具体问题抽象为01背包才是关键


这真的不能怪笔者拖更,这两道题真给我整麻了


文章目录

  • 代码随想录 | Day38 | 动态规划 :01背包应用 目标和&&一和零
    • 494.目标和(恰好等于背包容量求方案数)
      • 思路分析:
      • 1.回溯法
      • 2.01背包
        • 1.回溯暴力枚举
        • 2.记忆化搜索
        • 3.1:1翻译为动态规划
        • 4.滚动数组优化
    • 474.一和零
      • 思路分析:
      • 1.回溯暴力枚举
      • 2.记忆化搜索
      • 3. 1:1翻译为动态规划
      • 4.滚动数组优化

494.目标和(恰好等于背包容量求方案数)

[494. 目标和 - 力扣(LeetCode)](https://leetcode.cn/problems/partition-equal-subset-sum/description/)

思路分析:

设前面要加“+”的数和为p,前面要加“-”的数的和为q。

p+q=sum(数组所有元素的和)

p-q=target(要加正号的减去要加负号的)

2p=sum+target

p=(sum+target)/2

也就是说呢,我们要在nums数组里面找一个子集,让子集的和等于p,能找到几个就有几种方案

1.回溯法

本题也可以使用回溯暴力枚举,直接搜索nums里面的所有组合,等于target的就是答案。

这是组合总和的代码,当然是超时的

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);}// 如果 sum + candidates[i] > target 就终止遍历for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i + 1);sum -= candidates[i];path.pop_back();}}
public:int findTargetSumWays(vector<int>& nums, int S) {int sum = 0;for (int i = 0; i < nums.size(); i++) sum += nums[i];if (S > sum) return 0; // 此时没有方案if ((S + sum) % 2) return 0; // 此时没有方案,两个int相加的时候要格外小心数值溢出的问题int bagSize = (S + sum) / 2; // 转变为组合总和问题,bagsize就是要求的和// 以下为回溯法代码result.clear();path.clear();sort(nums.begin(), nums.end()); // 需要排序backtracking(nums, bagSize, 0, 0);return result.size();}
};

2.01背包

如何转换为01背包呢?

1.因为是子集,所以每个数只有选或者不选两个选项,不会有重复

2.就是把nums里面的数字当做物品,他们的价值和重量全是他们本身的数值

3.而我们背包的容量c就是target这个值,只要我们的方案可以刚好把背包给填满了,那说明背包里面放进去的nums[i]加和就是target

如果不是刚好填满,那肯定找不出子集加起来正好是target

转变为01背包后的递推公式变化?

dp/dfs表示从前 i 个数中从选一些数恰好组成 j 的方案数

就是从前i个数里面随便选,能正号填满容量j的方案数

只需要把01背包的max换成+即可

dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]];

dp[i][j]是由两个情况相加得到的。我们没有选第i件物品和选了第i件物品

选了那方案数就得加上在容量为j-w[i]的情况下的方案数

没选那方案数就得加上选上一个物品的时候在容量为j的情况下的方案数(上一个物品也分为选或不选)

这里是加法原理,选和不选第i个物品的情况是互斥的,我们要得到所有的方案数量就需要把它加起来

1.回溯暴力枚举

1.参数和返回值

i是物品编号

c是当前容量

nums是题数组

dfs(i,c) 表示从前 i 个数中从选一些数恰好组成 c 的方案数

int dfs(int i,int c,vector<int>& nums)

2.终止条件

1.i<0说明没有物品可以选了,如果当前容量正好等于0,那就返回1说明找到了一个合法方案

不等于0就返回0说明没找到

2.如果当前容量已经小于了物品所需的容量,那说明背包装不下,那就只能不选这个物品了,就返回不选这个物品的方案数量,即在前i-1个物品里面能凑够容量c的方案数

		if(i<0)if(c==0)return 1;elsereturn 0;if(c<nums[i])return dfs(i-1,c,nums);

3.本层逻辑

返回 在前i个物品中,选了第i个物品能满足c的方案数和没选第i个物品能满足c的方案数之和

return dfs(i-1,c-nums[i],nums)+dfs(i-1,c,nums);

完整代码

class Solution {
public:int dfs(int i,int c,vector<int>& nums){if(i<0)if(c==0)return 1;elsereturn 0;if(c<nums[i])return dfs(i-1,c,nums);return dfs(i-1,c-nums[i],nums)+dfs(i-1,c,nums);}int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;return dfs(nums.size()-1,target,nums);}
};
2.记忆化搜索

还是老样子

初始化dp为-1,如果不是-1说明计算过了直接返回

并且在返回之前给dp赋值

class Solution {
public:int dfs(int i,int c,vector<int>& nums,vector<vector<int>>& dp){if(i<0)if(c==0)return 1;elsereturn 0;if(c<nums[i])return dp[i][c]=dfs(i-1,c,nums,dp);if(dp[i][c]!=-1)return dp[i][c];return dp[i][c]=dfs(i-1,c-nums[i],nums,dp)+dfs(i-1,c,nums,dp);}int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;vector<vector<int>> dp(nums.size(),vector<int>(target+1,-1));return dfs(nums.size()-1,target,nums,dp);}
};
//lambda
class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;vector<vector<int>> dp(nums.size(),vector<int>(target+1,-1));function<int(int,int)> dfs=[&](int i,int c)->int{if(i<0)if(c==0)return 1;elsereturn 0;if(c<nums[i])return dp[i][c]=dfs(i-1,c);if(dp[i][c]!=-1)return dp[i][c];return dp[i][c]=dfs(i-1,c-nums[i])+dfs(i-1,c);};return dfs(nums.size()-1,target);}
};
3.1:1翻译为动态规划

注意这里把dp数组里面的下标i都加了1(重点)

因为这样做可以避免负数下标的出现

一种好理解的方式是dp[i][j]含义是在前i个物品里面选,容量刚好满足j

物品编号是1-i,而不是0-i

那为什么nums数组的下标没有加+1呢?

因为nums数组从0开始的,dp数组中的i,表示第i件物品,而第i件物品的重量和价值和nums[i-1]

dp[i+1][j]=dp[i][j]+dp[i+1][j-nums[i]];

关于初始化就看上面回溯的终止条件

i<0&&c==0就为1,那里的i是我们这里的i-1,因为我们给dp数组的i下标都加了1

也就是说

dp[i][0]全都为1

但这里只需要把dp[0][0]=1就行,其他的会在循环中给他赋值的,最后打印出来也确实是1

完整代码:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;vector<vector<int>> dp(nums.size()+1,vector<int>(target+1,0));dp[0][0]=1;for(int i=0;i<nums.size();i++)for(int j=0;j<=target;j++)if(j<nums[i])dp[i+1][j]=dp[i][j];elsedp[i+1][j]=dp[i][j]+dp[i][j-nums[i]];return dp[nums.size()][target];}
};
4.滚动数组优化
class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;vector<int> dp(target+1,0);dp[0]=1;for(int i=0;i<nums.size();i++)for(int j=target;j>=nums[i];j--)dp[j]=dp[j]+dp[j-nums[i]];return dp[target];}
};

474.一和零

474. 一和零 - 力扣(LeetCode)

思路分析:

这道题我也没想到怎么联系到01背包,或者说怎么应用

看了题解以后才了解到了

咱们平时的容量限制是物品i的重量

今天的容量限制是物品i(字符串strs[i])里面的0和1的数量,就是说有两个限制,一个是0的数量,一个是1的数量,说明这是一个三维数组,而每个物品i(字符串strs[i])的价值是多少?

因为求的是子集的数量,所以每一个字符串的价值都为1,就是放这个字符串i进去,就加1,不放就不加

举例:

比如说我们放的放的,背包容量只能装2个0和三个1了

那我们下一个strs[i]如果是 000111,有3个0,3个1那也放不进去

001111也不行

0000也不行

就是两个要都满足

比如0011,然后放进去以后最大的方案数+1

所以本质上就是01背包的容量多加了一个维度

递推公式直接仿照着写

dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]);
dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j - zeronum][k - onenum] + 1);

当前的最大数量那就是不放当前字符串和放了当前字符串这两种情况里面选一个最大值,如果我我们选了当前字符串那就+1,因为dp含义是在前i个字符串里面选的最多有j个0和k个1的子集中的字符串数量。

1.回溯暴力枚举

1.参数和返回值

i是物品编号,标识到了第几个字符串了

j是当前0的容量,k是当前1的容量

strs是题数组

dfs(i,j,k,strs) 含义是在前i个字符串里面选的最多有j个0和k个1的子集中的字符串数量

int dfs(int i,int j,int k,vector<string>& strs)

2.终止条件

1.i<0说明没有物品可以选了,如果当前容量正好等于0,那就返回1说明找到了一个合法方案

不等于0就返回0说明没找到

2.如果当前容量已经小于了物品所需的容量,那说明背包装不下,那就只能不选这个物品了,就返回不选这个物品的方案数量,即在前i-1个物品里面能凑够容量c的方案数

if(i<0)return 0;
if(j<zeronum || k<onenum)//0和1只要有一个不够就不放 return dfs(i-1,j,k,strs);

3.本层逻辑

返回当前的最大数量,就是不放当前字符串和放了当前字符串这两种情况里面选一个最大值

		int zeronum=0;int onenum=0;for(auto c:strs[i])if(c=='0')zeronum++;elseonenum++;if(j<zeronum || k<onenum)return dfs(i-1,j,k,strs);return max(dfs(i-1,j,k,strs),dfs(i-1,j-zeronum,k-onenum,strs)+1);

完整代码

当然是超时的

class Solution {
public:int dfs(int i,int c,vector<int>& nums){if(i<0)if(c==0)return 1;elsereturn 0;if(c<nums[i])return dfs(i-1,c,nums);return dfs(i-1,c-nums[i],nums)+dfs(i-1,c,nums);}int findTargetSumWays(vector<int>& nums, int target) {int sum=0;for(int c:nums)sum+=c;int p=sum+target;if (p < 0 || p % 2 != 0)return 0;target=p/2;return dfs(nums.size()-1,target,nums);}
};

2.记忆化搜索

还是老样子

初始化dp为-1,如果不是-1说明计算过了直接返回

并且在返回之前给dp赋值

class Solution {
public:int dfs(int i,int j,int k,vector<string>& strs,vector<vector<vector<int>>>& dp){if(i<0)return 0;int zeronum=0;int onenum=0;for(auto c:strs[i])if(c=='0')zeronum++;elseonenum++;if(dp[i][j][k]!=-1)return dp[i][j][k];if(j<zeronum || k<onenum)return dp[i][j][k]=dfs(i-1,j,k,strs,dp);return dp[i][j][k]=max(dfs(i-1,j,k,strs,dp),dfs(i-1,j-zeronum,k-onenum,strs,dp)+1);}int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<vector<int>>> dp(strs.size(),vector<vector<int>>(m+1,vector<int>(n+1,-1)));return dfs(strs.size()-1,m,n,strs,dp);}
};
//lambda
虚晃一枪,笔者懒得写了,大家自己写吧

3. 1:1翻译为动态规划

多加了初始化部分,就是物品1,能放下的地方初始化为1,放不下的初始化为0

		int zeronum=0;int onenum=0;for(auto c:strs[0])if(c=='0')zeronum++;elseonenum++;for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j<zeronum||k<onenum)dp[0][j][k]=0;elsedp[0][j][k]=1;

完整代码:

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<vector<int>>> dp(strs.size(),vector<vector<int>>(m+1,vector<int>(n+1,0)));int zeronum=0;int onenum=0;for(auto c:strs[0])if(c=='0')zeronum++;elseonenum++;for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j<zeronum||k<onenum)dp[0][j][k]=0;elsedp[0][j][k]=1;for(int i=1;i<strs.size();i++){zeronum=0;onenum=0;for(auto c:strs[i])if(c=='0')zeronum++;elseonenum++;for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j<zeronum||k<onenum)dp[i][j][k]=dp[i-1][j][k];elsedp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-zeronum][k-onenum]+1);}return dp[strs.size()-1][m][n];}
};

4.滚动数组优化

和01背包一样,先遍历物品

容量倒着遍历

删掉物品编号那一维

除了初始化为0不需要其他的初始化动作

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1,0));for(int i=0;i<strs.size();i++){int zeronum=0;int onenum=0;for(auto c:strs[i])if(c=='0')zeronum++;elseonenum++;for(int j=m;j>=zeronum;j--)for(int k=n;k>=onenum;k--)dp[j][k]=max(dp[j][k],dp[j-zeronum][k-onenum]+1);}return dp[m][n];}
};

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

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

相关文章

influxdb与LSM-TREE

一、什么是LSM-TREE 在一些写多读少的场景&#xff0c;为了加快写磁盘的速度&#xff0c;提出使用日志文件追加顺序写&#xff0c;加快写的速度&#xff0c;减少随机读写。但是日志文件只能遍历查询。不支持随机查询&#xff0c;提出使用LSM-TREE。除了利用磁盘顺序写之外&…

Mac保护电池健康,延长电池使用寿命的好方法

使用Mac的过程中&#xff0c;如何延长电池的使用寿命是大家非常关心的问题&#xff0c;而养成一个良好的充电习惯能够有效的延长电池的使用寿命 避免过度充电和过度放电能够有效的保护电池&#xff0c;因此长时间的充电与长时间放点都不可取&#xff0c;但是在日常的使用过程中…

AutosarMCAL开发——基于EB ResourceM模块

目录 一、ResourceM模块的作用以及原理1.ResourceM模块的作用2.单核系统运行原理a.上电复位b.启动代码执行c.应用程序加载d.应用程序执行 3.代码执行过程4.内存分配a.地址空间划分b.具体地址分配c.示例说明 4.多核系统运行原理a.MCU架构 二、EB配置介绍三、总结 一、ResourceM模…

【LeetCode】返回链表的中间结点、删除链表的倒数第 N 个结点

主页&#xff1a;HABUO&#x1f341;主页&#xff1a;HABUO &#x1f31c;钱塘江上潮信来&#xff0c;今日方知我是我&#x1f31b; 1.返回链表的中间结点 题目&#xff1a;给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。如果有两个中间结点&#xff0…

Netty篇(学习前言)

目录 一、为什么使用Netty 1. Netty编程相比NIO编程的优势 2. Netty 相比其它网络应用框架的优势 二、让我们走进Netty 1. 简介 2. 设计目标 3. 主要特点 4. Netty的作者 5. Netty 的地位 6. Netty 的优势 五、Netty版本说明 六、Netty架构设计 1. 线程模型基本介绍…

Ceph 学习指南 集群部署【 cephadm 】

文章目录 引言初识 Server SANServer SAN 和传统存储对比 Ceph 概述Ceph 的架构设计Ceph 的特点Ceph 块存储Ceph 文件系统Ceph 对象存储Ceph 介绍 Ceph 集群部署配置 aliyun 源配置时间同步配置 hosts 文件安装 docker配置免密登录ceph 集群部署ceph1 配置安装 python3安装 cep…

Linux篇(常见入门命令)

目录 一、开启终端 二、Linux命令格式 1. 什么是Linux 的命令&#xff1f; 三、Linux下的命令补全 四、切换用户 五、uname&#xff1a;查看操作系统信息 六、ls&#xff1a;查看目录下文件 1. 用法一 2. 用法二 3. 用法三 七、pwd&#xff1a;显示当前路径 八、cd&…

全面解析:网络协议及其应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 # 全面解析&#xff1a;网络协议及其应用 文章目录 网络协议概述定义发展历程主要优势 主要网络协议应用层协议传输层协议网络层…

02- 模块化编程-006 ADC0808数码显示对比

1、ADC0808 芯片介绍 ADC0808是一款集成的CMOS设备&#xff0c;包含8位模拟至数字转换器、8通道多路复用器和与微处理器兼容的控制逻辑。8位A/D转换器采用逐次逼近作为转换技术。转换器特点包括高阻抗斩波稳定比较器、256R电压分压器、模拟开关树和逐次逼近寄存器。8通道多路复…

【自动化测试】APP UI 自动化(安卓)-本地环境搭建

一、软件准备及版本介绍 软件版本JAVA-SDK1.8.0_181 python 3.10.10 Android SDK Tools 下最新版本即可&#xff0c;无特殊要求 PyCharm 2023.3.5&#xff08;下最新版本即可&#xff0c;无特殊要求&#xff09; 二、安装步骤及环境变量配置 2.1 Java安装及配置 1&am…

leetcode912.排序数组的题解

题目描述&#xff1a; 题目要求在不使用任何内置函数的情况下解决问题&#xff0c;时间复杂度为 O(nlog(n))。 笔者使用了快速排序&#xff0c;但是直接使用最原始的快速排序&#xff0c;有些特殊的测试用例会超时。 1&#xff09;如果数组本身基本有序&#xff0c;则使用原始…

安装Blender并使用

前言 该系列记录了如何用Blenderpro来构建自己的场景数据集&#xff0c;从环境搭建到后期构建数据集的整个流程 本文章是第一部分&#xff0c;BlenderPrc2的安装以及环境配置 部分参考https://blog.csdn.net/weixin_49521551/article/details/121573334 官方文档https://dlr…

json-server的使用(根据json数据一键生成接口)

一.使用目的 在前端开发初期&#xff0c;后端 API 可能还未完成&#xff0c;json-server 可以快速创建模拟的 RESTful API&#xff0c;帮助前端开发者进行开发和测试。 二.安装 npm install json-server //局部安装npm i json-server -g //全局安装 三.使用教程 1.准备一…

MySQL详细安装教程

一、从MySQL官网安装 可以翻译成中文看起来就舒服多了 下载并打开安装包&#xff0c;能看到版本是8.0.36&#xff0c;双击运行或者右键选择打开&#xff0c;打开后是一个安装向导&#xff0c;这个安装向导会先帮我们安装一个 mysql-installer 的程序&#xff0c;再通过该程序安…

qt QErrorMessage详解

1、概述 QErrorMessage是Qt框架中用于显示错误消息的一个对话框类。它提供了一个简单的模态对话框&#xff0c;用于向用户显示错误或警告消息。QErrorMessage通常用于应用程序中&#xff0c;当需要向用户报告错误但不希望中断当前操作时。它提供了一个标准的错误消息界面&…

Vue3安装、创建到使用

vue安装 npm install vuenext # 全局安装 vue-cli npm install -g vue/cli #更新插件 项目中运行 vue upgrade --nextvue create 命令 vue create [options] <app-name> options 选项可以是&#xff1a; -p, --preset <presetName>&#xff1a; 忽略提示符并使用已…

Linux 下执行定时任务之 Systemd Timers

不知道 ECS 因为什么缘故&#xff0c;上面安装的 MySQL 服务老是不定期挂掉&#xff0c;本来想通过 Linux 得 Cron 配置个半小时的定时检测任务&#xff0c;结果一直没有执行&#xff0c;因此又尝试使用了 Systemd Timers 进行了重新配置&#xff0c;简要做个记录。 Systemd Ti…

计算机网络:网络层 —— IP 多播技术

文章目录 基本概念IP多播地址和多播组 IP多播的类型硬件多播将IPv4多播地址映射为多播MAC地址 基本概念 多播&#xff08;Multicast&#xff0c;也称为组播&#xff09;是一种实现“一对多”通信的技术&#xff0c;允许一台或多台主机&#xff08;多播源&#xff09;发送单一数…

OuteTTS:基于纯语言建模的开源文本到语音合成项目,支持语音克隆等多种语音合成任务

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

C语言 | Leetcode C语言题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; int singleNonDuplicate(int* nums, int numsSize) {int low 0, high numsSize - 1;while (low < high) {int mid (high - low) / 2 low;mid - mid & 1;if (nums[mid] nums[mid 1]) {low mid 2;} else {high mid;}}return …