动态规划:将题目转换为01背包问题

news2024/9/25 17:14:51

文章目录

  • 494. 目标和
  • 474. 一和零

494. 目标和

力扣传送门:
https://leetcode.cn/problems/target-sum/

题目描述:

给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

动态规划解法:

解析:题目让我们在某一个或者几个位置加上符号,如果是一个负号,则就整个数组中只有正数和负数两种形式,我们不妨把这个数组看作是两个部分的和,其中一部分是正数,另一部分是负数。

left:表示正数序列的组合
right:表示负数序列的组合

如:+1 + 1 + 1 + 1 - 1 = 3 :
则可以表示为left:是[0,3]的序列,这个序列都是正数;right:是(3,4]的组合(不包括三),他代表的是负数的子序列。

我们可以根据题意得到这两个等式:

  • 等式一: left - right = sum(数组的原始和)
  • 等式二: left + right = target(目标)

正数减去负数,得到的是数组原始的元素的和;正数加上负数表示的我们题目中给到的目标target。

所以,可以进一步推出此公式:
由等式一得:right = left -sum,将right带入到等式二中,得:
left + left -sum = target:

  • 这就是我们所得到的一个关键公式left = (target + sum)/2

得到了这个公式有什么用呢?

left = (target+sum)/2

由于 target 和 sum都是固定的,所以只有我们的left是可变的,而left又代表着什么呢??

我们在刚才说过,left代表的是一个正数序列的组合,什么意思?

意思就是在nums[i]中选取元素,使得元素之和等于left,求所选取的元素的方案数。

我们可以把这道题看作是一个01背包的问题。

size = left

因此我们 的size 就是背包的容量,本题也就是要求:
装满容量为size的背包,一共有几种方案?


我们把数组的元素nums[i]看作物品,把不同情况的元素之和size看作是背包的容量。

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

创建dp[i][j]二维数组,其中 i 表示从数组中选取的nums[i]元素,即把某个元素看作物品;j表示背包的当前容量 j,也就是各个情况目标元素之和。
dp[i][j] 表示在数组 nums的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数

  1. 确定递推公式
  • 不选取某一个元素nums[i]: dp[i][j] = dp[i-1][j]
  • 选取某一个元素nums[i]: dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]

最终我们的dp[n][size]就是答案。

  1. dp数组的初始化:

dp[0][0] = 1

  1. dp数组的遍历:

首先遍历物品i,其次遍历背包j

  1. 图例推导dp过程

在这里插入图片描述
由此可见,我们的dp[i][j]其实就是其上方的元素与左上方的元素之和。

代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=accumulate(nums.begin(),nums.end(),0);
        //特殊情况1
        if (sum<abs(target))
        {
            return 0;
        }
        //特殊情况2
        if ((target+sum)%2==1)
        {
            return 0;
        }
        int size=(target+sum)/2; 
        int n=nums.size();
        vector<vector<int>> dp(n+1,vector<int>(size+1));
        dp[0][0]=1;
        for (int i=1;i<=n;i++)
        {
            int num=nums[i-1];
            for (int j=0;j<=size;j++)
            {
               if (j<num) dp[i][j]=dp[i-1][j];
               else dp[i][j]=dp[i-1][j]+dp[i-1][j-num];
            }
            
        }
        for (auto& x:dp)
            {
                for (auto& y:x)
                {
                    cout<<y<<" ";
                }
                cout<<endl;
            }
        return dp[n].back();
    }
};

dp的优化:滚动数组思想

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=accumulate(nums.begin(),nums.end(),0);
        //特殊情况1
        if (sum<abs(target))
        {
            return 0;
        }
        //特殊情况2
        if ((target+sum)%2==1)
        {
            return 0;
        }
        int size=(target+sum)/2;   
        vector<int> dp(size+1);
        dp[0]=1;
        int n=nums.size();
        for (int i=1;i<=n;i++)
        {
            int num=nums[i-1];
            for (int j=size;j>=num;j--)
            {
                dp[j]+=dp[j-num];
            }
        }
        return dp.back();
    }
};

474. 一和零

力扣传送门:
https://leetcode.cn/problems/ones-and-zeroes/description/

本题一个三维动态规划 + 01背包的题目。
什么是三维动态规划?

  • 二维动态规划01背包 dp[i][j]: 有两个维度,即物品 i 和背包容量 j
  • 三维动态规划01背包 dp[k][i][j]:有三个维度,本题中:k表示字符串数组中前k个字符串,i 表示0的个数,j 表示1的个数。

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

dp[k][i][j] : 表示在前 k 个字符串中,使用最多 i 个0,j 个1,可以得到的目标字符串的个数。

  1. 递推公式的确定
  • 不选这个字符串:dp[k][i][j] = dp[k-1][i][j]

    • 此时的情况: i < zeronum && j < onenum ,即规定的 0,1的数量不足以容纳字符串中的0,1的个数,所以不能选取这个字符串
  • 选择这个字符串:dp[k][i][j] =max(dp[k][i][j], dp[k-1][i-zeronum][j-onenum] + 1)

    • 同理, i >= zeronum && j >= onenum ,规定最大的i,j,可以容纳字符串中的0,1的个数,所以可以选择这个字符串。
  1. dp数组的初始化

dp[0][0][0] = 0,没有字符串,只能初始化为0

  1. dp数组的遍历过程

三维: 首先遍历字符串,接着在字符串中处理二维dp的物品与容量的关系。


代码如下:

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int sn=strs.size();
        vector<vector<vector<int>>> dp(sn+1,
            vector<vector<int>>(m+1,vector<int>(n+1)));
        for (int k=1;k<=sn;k++)
        {
            //每次统计当前字符串中01的数量
            int zeronum=0,onenum=0;
            for (auto& num:strs[k-1])
            {
                if (num=='0') zeronum++;
                else if (num=='1') onenum++;
            }
            //二维的dp操作
            for (int i=0;i<=m;i++)
            {
                for (int j=0;j<=n;j++)
                {
                    dp[k][i][j]=dp[k-1][i][j];
                    if (i>=zeronum && j>=onenum)
                    {
                        dp[k][i][j]=max(dp[k][i][j],
                            dp[k-1][i-zeronum][j-onenum]+1);
                   }
                }
            }
        // }
        // for (auto& x:dp)
        // {
        //     for (auto& y:x)
        //     {
        //         for (auto& z:y)
        //         {
        //             cout<<z<<" ";
        //         }
        //         cout<<endl;
        //     }
        //     cout<<endl;
        // }
        return dp[sn][m][n];
    }
};

空间优化: 二维数组版本

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
        for (string str : strs) { // 遍历物品
            int oneNum = 0, zeroNum = 0;
            for (char c : str) {
                if (c == '0') zeroNum++;
                else oneNum++;
            }
            for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

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

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

相关文章

easyrecovery2023最新免费版电脑数据恢复软件使用教程

easyrecovery2023版能实现多种不同格式的完成修复和进程的解决&#xff0c;能进行数据的操作和保存解决完成&#xff0c;通过不同的内容进行操作&#xff0c;能解决大部分的使用问题&#xff0c;能安全的进行保存。easyrecovery免安装版对于多种格式下的内容&#xff0c;能对多…

nacos注册中心和配置中心

nacos注册中心和配置中心 nacos 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 nacos官方文档&#xff1a;https://nacos.io/zh-cn/ 相关概念&#xff1a;https://nacos.io/zh-cn/docs/architecture.html nacos是AP架构,注重可用性和分区容错性&#…

腾讯云双十二服务器2核2G、2核4G、4核8G、8核16G、16核32G配置价格表出炉

现在腾讯云服务器2核2G、2核4G、4核8G、8核16G、16核32G配置价格表已经出来了&#xff0c;大家可以参考一下。腾讯云轻量应用服务器为轻量级的云服务器&#xff0c;使用门槛低&#xff0c;按套餐形式购买&#xff0c;轻量应用服务器套餐自带的公网带宽较大&#xff0c;4M、6M、…

​软件测试之“支付功能”测试

01 测试思维 要分析测试点之前&#xff0c;我们先来梳理一下测试思维。总结来说&#xff0c;任何事物的测试思路都可以总结如下&#xff1a; 第一步&#xff1a;梳理产品的核心业务流程&#xff1a;明白这是个什么项目&#xff0c;实现了什么业务&#xff0c;以及是怎么实现的…

电动汽车电池换电站选址与定容(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;主要研究方向是电力系统和智能算法、机器学…

python学习笔记(13)---(IO对象序列化)面向对象

目录 面向对象---第十一章 IO对象序列化 1.IO流&#xff08;IO stream&#xff09; 2.open&#xff08;&#xff09;方法 3.写入方法&#xff1a;write() 4.对象序列化 面向对象---第十一章 IO对象序列化 1.IO流&#xff08;IO stream&#xff09; &#xff08;1&…

编译原理实验三:算符优先分析算法的设计与实现

实验三 算符优先分析算法的设计与实现 一、 实验目的 根据算符优先分析法&#xff0c;对表达式进行语法分析&#xff0c;使其能够判断一个表达式是否正确。通过算符优先分析方法的实现&#xff0c;加深对自下而上语法分析方法的理解。 二、 实验要求 1、输入文法。可以是如下…

Java 图片上传后为什么会自动旋转90度?

问题&#xff1a; 用户反馈上传后的图片方向不对&#xff0c;起初怀疑是本身图片方向有问题&#xff0c;但是用windows图片查看器打开图片方向是"正常"显示的? 分析&#xff1a; windows默认的图片查看器已经帮我们自动旋转展示了&#xff0c;我们在手机横拍或者扫…

【Vue核心】7.事件处理

事件处理的基本使用 绑定监听 v-on:xxx“fun” xxx“fun” xxx“fun(参数)” 默认事件形参: event 隐含属性对象: $event 绑定方法说明 使用v-on:xxx 或xxx绑定事件,其中xxx是事件名;事件的回调需要配置在methods对象中,最终公在vm上;methods中配置的函数,不要用箭头函…

python 调试IGH库

如何通过python来调试IGH的库呢&#xff1f; 可以使用如下的代码&#xff0c;测试请求主站&#xff0c;把主站变成激活状态。其他的函数也可以类似的一步一步调用。 结果如下&#xff1a; from ctypes import * ighCDLL("/home/cheni/lichuan_bujin/libethercat.so&quo…

开传奇大概需要什么条件

《热血传奇》是盛趣游戏2001年推出的一款大型多人在线角色扮演游戏&#xff08;MMORPG&#xff09;。 该游戏具有战士、魔法师和道士三种职业&#xff0c;所有情节的发生、经验值取得以及各种打猎、采矿等活动都是在网络上即时发生。 《热血传奇》包括白天、黑夜、贸易、物品等…

c++多模块化划分算法MMM(单链接、全链接、均链接)

文章目录题目1c代码一、实验目的二、实验描述3.1 题目13.1.1 单链接3.1.2 全链接3.1.3 均值链接3.1.4 划分结果统计&#xff1a;3.2 题目23.2.1 单链接3.2.2 全链接3.2.3 均值链接3.2.4 划分结果统计&#xff1a;题目2c代码github地址 代码地址 题目1c代码 #include<cstdi…

关于Servlet编程(2)

1.常用类的关键API介绍 Servlet中常见的类有三个.介绍过了HttpServlet类.我们再来看看另外两个. HttpServletRequest 这个类对应到发送的HTTP请求. 方法描述String getProtocol()返回请求协议的名称和版本String getMethod()返回请求的 HTTP 方法的名称 (GET,POST 或 PUT)S…

java计算机毕业设计ssm医院病人信息管理系统60k18(附源码、数据库)

java计算机毕业设计ssm医院病人信息管理系统60k18&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#…

summernote富文本编辑器和jquery.validate冲突报错的解决方案

先看问题: 当我在富文本输入并移出鼠标焦点的时候, 控制台抛出错误Cannot read properties of undefined (reading replace), 而导致jquery.validate无法对其他表单组件进行校验 1.定位问题: jquery.validate的初始化是监听了表单$("#addOrEditForm").validate(),…

Nacos的服务注册之服务端

上节讲到了nacos的客户端,通过把实例信息封装成instance,调用服务端的接口:/instance,进行注册. 接下来,我们一起看看服务端是怎么处理客户端发来的请求的. 服务端 在nacos-naming这个模块中,在这个模块里边有一个InstanceController 其中在com.alibaba.nacos.naming.contro…

Netty_03_ByteBuf和网络中拆包粘包问题及其解决

文章目录一、前言二、ByteBuf&#xff08;Netty API中定义的数据类型&#xff09;2.1 ByteBuf2.1.1 ByteBuf创建的方法有两种2.1.2 ByteBuf的存储结构2.1.3 ByteBuf中常用的方法APIReader相关方法Write相关方法Write可能导致扩容2.2 ByteBuf代码(演示读写指针移动和扩容)2.2.1 …

RabbitMQ:基础概述

RabbitMQ 是一个消息中间件&#xff0c;它接收消息并且转发&#xff0c;是“消费-生产者模型”的一个典型的代表&#xff0c;一端往消息队列中不断的写入消息&#xff0c;而另一端则可以读取或者订阅队列中的消息。 RabbitMQ 于 2007 年发布&#xff0c;由 erlang 语言进行开源…

37_软件I2C通信实验

目录 I2C通信协议 多主机I2C总线系统结构 I2C协议 应答信号ACK 数据有效性 数据传输 I2C设备地址 I2C通讯整个过程 硬件连接 EEPROM(24C02) 24C02字节写时序 24C02字节读时序 实验源码 I2C通信协议 I2C(IIC,Inter-Integrated Circuit),两线式串行总线,由PHILIPS公…