1.题目链接:1049. 最后一块石头的重量 II
题目描述:
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
解法:
1.五步曲:
①本题求的是返回最小可能的重量。那么我们就将这堆石头分成尽可能重量相近的两堆,然后他们相撞,就会得到最小的可能重量。
②将其类比成背包问题就是,向容量为Sum/2的背包中尽可能装石头,这样就得到了尽可能装满后的价值即dp[j]。故dp[j]的含义就是,向容量为j的背包中尽可能装石头,得到的价值就是dp[j].
③递推公式:dp[j] = max(dp[j-stones[i]] + stone[i]---装了, dp[j---没装,价值不变]) --- 根据01背包的1维解法类比推导过来的。
④初始化:因为根据前面的值得到的dp[j],所以初始化dp[0] = 0---因为容量为0,所以价值为0。其他位置,因为根据递推公式,要和本身比较,不能覆盖且价值要大于0,所以取0。
⑤遍历顺序:先物品后背包;背包倒序。
⑥最后返回sum - dp[sum/2] - dp[sum/2]
2.步骤:
①创建dp[],长度为Sum/2+1
②初始化dp[0] = 0;
③遍历{递推公式;}
④此时我们把dp[Sum/2]求出来了,即是一堆石头的量。因为Sum/2是向下取整,所以Sum-dp[Sum/2]一定大于dp[Sum/2],即背包容量小,装的石头重量就小。
⑤最后返回Sum - dp[Sum/2] - dp[Sum/2]就是两堆石头相碰后的最小重量。
下面为代码(java):
2.题目链接:494. 目标和
题目描述:
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
解法:
1.五步曲:
①此题要求的是结果等于target的不同表达式的数目。我们可以将其也分成两部分,正数集left,负数集right。
②left + right = sum;left - right = target;left = (sum+target)/2 --- 即我们要从nums中找出和为left的表达式共有多少种。类比成背包问题就是选取物品放到背包容量为left的背包中,选取物品的方法有多少种。故dp[j] 就表示填充容量为j的背包,共有dp[j]种填充方法。
③递推公式:dp[j] = ? 举例容量为5,如果背包中有1,那么有dp[5-1]种方式;有2,就有dp[5-2]种方式,有nums[i],就有dp[j-nums[i]]种方式。所以dp[j] += dp[j-nums[i]] --- 即将所有的情况都加起来,之后遇到填充背包求有多少种填充方法的时候是一样的递推公式。
④初始化:因为根据前面的求,所以要初始化dp[0] = ?即背包容量为0的时候,有多少种填充方法,1种---即没填充。其他位置初始化为什么呢?初始化为0,因为我们递推公式是将所有情况加一起用到了dp[j] += dp[j-nums[i]],如果其他位置初始化不为0的话会影响到递推公式的推导,所以为0.
⑤遍历顺序:先物品,后背包。背包倒序。
⑥特殊情况:如果(sum+target) % 2 != 0,说明我们需要从nums中找到正数集和有小数才能满足target,但是nums中都是整数,不可能是小数,就说明了不可能找到满足target的方法集返回0.
⑦如果target小于0,且target+sum<0,说明target太小了,从nums中取值根本不能满足,所有返回0.
⑧可能有疑惑了,如过target>0且target>sum呢?用不用判断呢?因为我们在求bagsize的时候 = target+sum 除以2,如果target大于sum,最后的值也大于sum,而在初始化的时候我们就已经将dp[bagSize] 初始化成0了,而根据递推公式它一直都是0,所以不用再判断了。
2.步骤:
①先求sum
②判断不能整除返回0.
③判断target为负数且和sum的和也小于0,返回0.
④定义bagsize = (sum + target)/2 。创建dp数组,长度为(sum+target)/2 +1.
⑤初始化dp[0] = 1;
⑥遍历{dp[j] += dp[i-nums[i]];}先物品后背包,倒序背包。
⑦最后返回dp[bagSize]
下面为代码(java):
3.题目链接:474. 一和零
题目描述:
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
解法:
1.五步曲:
①此题要求最大子集长度。类比于背包问题,就相当于尽量往背包里装物品,求最多能装多少个物品。那么背包容量是多少呢?是m个0和n个1,因为只有背包容量越大,可能装下的物品就越多。而此时背包容量是二维的所以dp数组创建成二维的,dp[i][j]表示的是尽可能向容量为i个0,j个1的背包里装物品,最多能装dp[i][j]个物品。
②递推公式:dp[i][j] = max(dp[i-x][j-y] + 1, dp[i][j])为什么呢?要么装要么不装,不装就说明没放物品,说明物品数目没变,即dp[i][j]。装了说明把剩余容量取个数+1---即新装了一个。
③初始化:因为根据前面的值求的所以初始化dp[0][0] = 0,其余位置因为要和本身比较,不能覆盖又不能为负数所以为0.
④遍历顺序:先物品后背包;背包倒叙。
⑤最后返回dp[m][n]
2.步骤:
①创建dp[m][n]
②初始化dp[0][0] = 0
③遍历物品先增强for遍历字符数组的字符串,再将字符串变成char数组,再增强for每个字符串的字符,如果字符为0,x++;否则y++。这样就统计出了每个字符串的重量x,y。
④遍历背包for(m){
for(n){
dp[i][j] = max(dp[i-x][j-y] + 1, dp[i][j])
⑤最后返回dp[m][n]
下面为代码(java):
4.总结:
①最后石头重量 --- 给定一个容量(sum/2)的背包,尽可能的向背包里放。总统思路就是分成尽可能相近的两堆石头,然后相撞。递推公式 dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])。不放价值不变,放了价值变。
②目标和 --- 给定一个容量的背包((sum+target)/2),求将其放满有多少种方法。总统思路将集合分为正数集和负数集,然后数学推导得到背包容量 = (sum+target)/2。要注意递推公式:dp[j] += dp[j- nums[i]],即以后涉及到求放进背包中有多少种方法,都这样求。
③10和 --- 给定一个容量的背包(m个0,n个1),尽可能的向背包里放,求最多能放多少个物品。递推公式:dp[i][i] = max(dp[i-x][j-y] + 1, dp[i][j]).不放个数不变,放了个数变。
④第一个和第三的递推公式都是根据01背包1维解法来的(即根据放与不放的价值变化或者个数变化),而2呢要计算有多少种方法是一种新的推导过程,计算的是方法数目。