难度:中等
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
解释:
一共有5种方法让最终目标和为3。
提示:
前者是将集合分成两个相等的子集,后者是将集合分成两个尽可能相等的子集
共同点是什么?都是先把问题转换成将当前题目给的数组集合一分为二
因此,本题要往01背包问题上靠,也要先转换为一个将集合划分成两部分的问题
怎么转呢?
题目要在一个非负整数数组nums中的任意一个整数前加正负号,实现所有元素相加后等于目标值target,最后统计一共有多少种相加的方法(即一共有多少种放正负号的方法)
那么我们就可以把数组元素分为两个子集,一个子集中的元素前面都加正号,另一个子集则都加负号
这不就有两个子集了嘛(md这正常人能想到?)
设加负号的子集为 negativeSign, 加正号的子集为 plusSign
注意,此时我们讨论的两个子集都是已经通过dp划分好的,里面不带正负号
那么,两个子集的元素相加应该等于非负整数数组nums的元素之和sum
两个子集的元素相减应该等于目标值target
抽象为公式如下:
① plusSign + negativeSign = sum;
② plusSign - negativeSign = target;
合并一下可以得到: plusSign = (sum + target) / 2;
在01背包问题中,只需用一个子集充当背包即可,因此这里可以选择 加正号的子集plusSign 作为背包
以示例 nums: [1, 1, 1, 1, 1], target: 3 来说
转换为背包问题后,背包的容量为 (5+3)/2 = 4 ,所谓的"物品"就是nums数组中的元素
当然,这里用除法就会涉及不能整除的情况
若不能整除,代表该数组nums找不到能够组合成目标值target的方法,直接return 0
此时,问题就转换成了:使用非负整数数组nums中的元素装满背包有几种方法?
(注意,本题要找的是有几种装满背包的方法)
区分一下之前做的背包问题的目标
单纯的01背包问题:装满某个背包时,物品的最大价值;
分割等和子集:往背包放入物品后,背包的最大重量(换句话说就是能不能用物品把背包装满,能就return true)
最后一块石头:往背包装物品,能装下的最大价值(能装多少装多少)
老规矩,先回顾一下经典01背包问题的dp数组定义
dp[j]: 背包容量为j时,装满背包的最大价值为dp[j]
转换一下,本题的dp数组含义可以定义如下:使用所给的所有物品,装满容量为j的背包有dp[j]种方法
怎么推导出dp[j]呢?
这里要通过"物品"的角度来想,例如,当前如果有一个物品(nums中的一个元素)要放入背包,假设背包容量是5(与示例保持一致, nums元素为5个1)
那么这个物品一定会放入背包中,因此也一定会占用掉背包的一部分容量,占用掉的容量是 j - nums[i]
根据dp数组的含义,在有一个物品确定放入的情况下,dp[5]就会转变为dp[5 - 1],也就是dp[4]
有点乱?那再用直接一点的话描述一下上面发生的事情:
1、最开始,背包容量是5,此时按dp数组的定义,装满该背包会有dp[5]种方法;(因为还有五个容量,你随便怎么装,所有的方法就表示为dp[5])
2、当已经有一个"物品"确定放入容量为5的背包时,背包容量缩减为4(不管你开始没开始往里面放,先给你预留了),还是按定义,此时装满该背包会有dp[5-nums[0]]种方法(即dp[5 - 1] = dp[4])
3、当已经有两个"物品"确定放入容量为5的背包时,背包容量缩减为3(不管你开始没开始往里面放,先给你预留了)
按定义,此时装满该背包会有dp[5-nums[0]-nums[1]]种方法(即dp[5-1-1] = dp[3])
后面的情况以此类推
注意,一定要结合dp数组的定义
这里的
dp[某某]指的是在"某某"容量下放入物品时,所有方法的集合
简单概括一下,例如:dp[j],j为5,
那凑整dp[5]有多少方法呢?(即dp[5]怎么求)
就把所有的 dp[j - nums[i]] 累加起来即可
也就是dp[5] = dp[4] + dp[3] + dp[2] + dp[1] + dp[0]
总结为递推公式就是:
dp[j] += dp[j - nums[i]]
该递推公式很重要,在用背包解决排列组合问题时还能用
一切结合dp数组的含义:装满容量为j的背包有dp[j]种方法
来看dp[0]的情况
dp[0]即被包容量为0时装满背包的方法数量,这里又可以细分为两种情况:要装的物品重量不为0和重量为0
那么实际上我们是无法将该物品装入容量为0的背包中的,那么是不是就意味着在该种情况下,dp[0] = 0 了呢?
我认为也不是,因为背包容量为0是一种特殊情况,此时不论你往不往里面放东西(或者放不放得进),背包都已经处于放满状态,因此
dp[0]应该是默认有一种方式装满的,那就是什么也不放
由上述分析可知,dp[0]应该初始化为1,即dp[0] = 1;
接着上面的分析,若背包中物品重量为0, 假设:[0,0,0,0,0], target = 0
那这些0就还是可以往背包里面放的(放不放都一样),并且不同的物品(重量为0)往背包放就算是一种不同的放法
因此,dp[0]就是这五个重量为0的物品不断组合放入背包内的组合方式的种类数量
大概有32种,于是dp[0] = 32
以上分析是建立在认同dp[0]应该初始化为1的情况下成立的(因为其他情况都是基于的dp[0] = 1推导出来的)
说了这么多,无非就是像说明清楚dp[0]初始化为1的可行性,记住本题 dp[0] = 1 就行
仍然遵循先遍历物品(nums),后遍历背包容量的顺序,且背包容量的遍历方向是倒序的
这里在逻辑上与之前涉及重量的问题不太一样,下面手动推导一遍
(输入:nums: [1, 1, 1, 1, 1], target: 3)
如图所示为遍历过程
注意dp数组的含义,使用所给的所有物品,装满容量为j的背包有dp[j]种方法
这里有两个关键点:1、需要使用所有的物品;2、装满
- 不论遍历的过程如何,最终我们需要求的是把所有物品放入容量为j的背包的方法,因为遍历物品的过程是一个一个遍历的,所以放入所有物品的方法种类也是由最开始的情况不断累加到最后才能得到的
- 一定要能够装满当前容量才算是一种方法,比如在容量为4的情况下,目前遍历到第一个物品(也就是只有一个物品),无论如何是放不满4个容量的,因此就算能够放入当前的一个物品,也不能算一种方法
说一下"装满方法"是怎样计算的
因为01背包问题中,每个物品只能使用一次,那么在当前物品能够装满当前容量的前提下,使用相同物品以不同顺序放入背包的方法应该视作同一种方法
什么意思呢?就是说假设现在遍历到了nums[2],我们手头上有3个物品,此时容量遍历到2的话,理论上我们有以下放入的方式:
nums[0] nums[1] nums[2]
nums[0] nums[2] nums[1]
nums[1] nums[0] nums[2]
nums[1] nums[2] nums[0]
nums[2] nums[1] nums[0]
nums[2] nums[0] nums[1]
其中,有一半的放入方式重复使用了物品,因此是不计入方法种类
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
//计算数组元素之和
int sum = 0;
for(auto num : nums) sum += num;
//判断两种无解的情况
//1、所给的target已经大于sum
//2、(sum + target) / 2不能整除,即计算背包容量时不能整除
if(abs(target) > sum) return 0;//取绝对值
if((sum + target) % 2 != 0) return 0;
//计算背包容量
int bagSize = (sum + target) / 2;
//定义dp数组
vector<int> dp(bagSize + 1, 0);
//初始化dp数组
dp[0] = 1;
//遍历dp数组
for(int i = 0; i < nums.size(); ++i){//遍历物品num
// 如果当前背包容量小于物品重量,换一个物品继续遍历容量(所以第二层循环的条件是j >= nums[i])
// 每一个元素一定是不可重复放入,所以从大到小遍历
for(int j = bagSize; j >= nums[i]; --j){//遍历背包容量
dp[j] += dp[j - nums[i]];
}
}
return dp[bagSize];
}
};
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
示例 1:
示例 2:
提示:
这题有点绕的其实,刚上手的话很容易将m、n看成两个容器
其实这样想是错误的,本题实质上还是01背包问题,只不过这个背包有"两个维度"
什么意思呢?我解释一下
先来说题意吧,题目要求是:m代表字符串中0的个数,n代表字符串中1的个数
然后,题目规定一组m、n,要求从字符串数组strs中找到能够满足m、n的最大子集,并返回该子集的大小
拿示例1来看,strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
最多有 5 个 0 和 3 个 1 的strs中的最大子集是 {"10(m=1,n=1)","0001(m=3,n=1)","1","0"}
该子集的大小是4,因此结果值返回的是4
看出来了吗?其实题目规定的"m = 5, n = 3"就是在设置背包的容量
确定了背包就好办了,下面就套五部曲解决问题
注意,这里题目是要求最大子集个数,也就是背包中物品的个数
那么dp数组可以定义如下
dp[i][j]: 在背包"容量"(这里的容量指的是strs中一个子字符串中0、1的个数)为i、j时能够装下物品的最大个数
虽然这里需要把dp数组设置成二维的,但其实本质上和之前的一维01背包问题没有区别
如果不好理解的话还是可以把dp数组看成是个一维的,例如dp[G],G是背包的容量,只不过G由两个部分组成,一部分是i,一部分是j
(这里也可以把G想象成strs中一个子字符串,例如"10")
因为本题只是给放入背包的物品多增加了一个维度,所以递推公式可以参考标准01背包问题的递推公式(一维)
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
那么对照着本题的递推公式就是
dp[i][j] = max(dp[i][j], dp[i - zeroNums][j - oneNums] + 1(物品数量));
解释一下,
当我们确定放入一个新的子字符串(假设是"10"),那么此时背包容量(i、j)就要对应减少子字符串中0的个数(zeroNums)、子字符串中1的个数(oneNums),(zeroNums, oneNums)即为该子字符串(物品)的重量
当然,此时包内的物品数量要加1(类比之前的递推公式,背包总价增加)
推导dp[i][j]时,有两种情况:放东西和不放东西
这两者取能够使物品个数最大的那种情况就行,即上面的递推公式
dp[0][0]时要初始化为0,即容量为(0,0)时,一个也装不下
然后其余的部分也要初始化为0,为了防止递推值被初始值覆盖(详见)
和普通的01背包的一维解法一样,这里也是先遍历物品(子字符串),然后倒序遍历背包容量(i,j)(对应(zeroNums, oneNums))
核心代码如下,结合上面的图来解释
for (string str : strs) { // 遍历物品,即子字符串,例如"10"
int oneNum = 0, zeroNum = 0;
for (char c : str) {//统计字符串中0、1的数量
if (c == '0') zeroNum++;
else oneNum++;
}//以下是遍历背包容量(i,j)
//如果当前背包容量(i,j)小于物品重量(zeroNum,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);
}
}
}
对应到图中就是,我们第一轮遍历是从最下面的一行开始的,从右往左倒序遍历
根据递推公式有:dp[3][3] = max(dp[3][3], dp[3 - 1][3 - 1] + 1);(此时子字符串是"10")
因为dp[3][3]的初始值为0,所以dp[2][2]+1肯定要大一些,故dp[3][3] = dp[3 - 1][3 - 1] + 1;
那dp[2][2]又是多少?还是用递推公式去算,得到dp[2][2] = max(dp[2][2], dp[2 - 1][2 - 1] + 1);,取决于dp[1][1]
同理,dp[1][1] = max(dp[1][1], dp[1 - 1][1 - 1] + 1);,最后可以算出dp[1][1]=dp[0][0]+1=1
所以,
dp[2][2]=1+1=2
dp[3][3]=2+1=3
此时可以得到图中dp[3][3]处的递推值,同理可以把整个dp数组的递推值计算出来,结果如上图所示
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
//定义dp数组,并初始化
//二维数组,行列分别是0、1(可以颠倒)
vector<vector<int>> dp(m + 1, vector(n + 1, 0));
//遍历dp数组
for(string str : strs){//遍历物品(子字符串)
int zeroNums = 0, oneNums = 0;
//统计字符串中的01个数
for(char c : str){
if(c == '0'){
zeroNums++;
}else oneNums++;
}
for(int i = m; i >= zeroNums; --i){//遍历背包容量(i,j),倒序
for(int j = n; j >= oneNums; --j){//i\j遍历顺序可以更换,因为本质上还是容量
dp[i][j] = max(dp[i][j], dp[i - zeroNums][j - oneNums] + 1);
}
}
}
return dp[m][n];
}
};
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有, 也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g