今天来对Lintcode上面的背包专题来个总结,除了付费的题全都做完了,就来回顾一下;
不得不说,做题自己悟永远是王道,北大版的《背包九讲》我是硬生生没有看下去,衰······
言归正传,让我们开始正文,这篇文章会贼长的我感觉······
一.不可分割不可重复使用的背包问题
- 背包问题
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
int backPack(int m, vector<int> &A) {
// write your code here
vector<int>dp(m+1,0);
for(int i=0;i<A.size();i++)
{
for(int j=m;j>=A[i];j--)
{
dp[j]=max(dp[j],dp[j-A[i]]+A[i]);
}
}
return dp.back();
}
- 背包问题 II
中文
English
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?
int backPackII(int m, vector<int> &A, vector<int> &V) {
// write your code here
vector<int>dp(m+1,0);
for(int i=0;i<A.size();i++)
{
for(int j=m;j>=A[i];j--)
{
dp[j]=max1(dp[j],dp[j-A[i]]+V[i]);
}
}
return dp[m];
}
- 卡牌游戏 II
中文
English
你跟你的朋友在玩一个卡牌游戏,总共有 n 张牌。每张牌的成本为 cost[i] 并且可以对对手造成 damage[i] 的伤害。你总共有 totalMoney 元并且需要造成至少 totalDamage 的伤害才能获胜。每张牌只能使用一次,判断你是否可以取得胜利。
bool cardGame(vector<int> &cost, vector<int> &damage, int totalMoney, int totalDamage) {
// Write your code here
vector<int>dp(totalMoney+1,0);
dp[0]=0;
for(int i=0;i<cost.size();i++)
{
for(int j=totalMoney;j>=cost[i];j--)
{
dp[j]=max(dp[j],dp[j-cost[i]]+damage[i]);
}
}
if(dp[totalMoney]>=totalDamage)
{
return true;
}
else
{
return false;
}
}
- 背包问题 V
中文
English
给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次
int backPackV(vector<int> &nums, int target) {
// write your code here
vector<int>cp(target+1,0);
cp[0]=1;
for(int i=0;i<nums.size();i++)
{
for(int j=target;j>=nums[i];j--)
{
cp[j]+=cp[j-nums[i]];
}
}
return cp[target];
}
先来这四道题,其实这四道题是一个类型的题目,特点: 物品不能重复使用;物品不得分割。
其中,物体不能分割是次要的,而程序主要需要应付是“不能重复使用!”,因此,才会使用形如
for(nums[i],i++)
{
for(target,target--)
}
的循环,内嵌循环之所以需要由大到小的原因很多博客都讲过了,我还是想赘述一遍。。。
如果从小到大,同一物体就有可能出现在某一较大容器中两次(只可意会啊果然)。
这还是我在夜游成府路的时候想出来的。。。
二.不可分割可重复的背包问题
当物品可以重复使用时,就可以放飞自我了!
内嵌循环由小到大,并且可以改变内外循环的顺序!
举例:
562. 背包问题 IV
给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次
int backPackIV(vector<int> &nums, int target) {
// write your code here
vector<int>dp(target+1,0);
dp[0]=1;
for(int i=0;i<nums.size();i++)
{
for(int j=0;j<=target;j++)
{
if(j>=nums[i])
{
dp[j]+=dp[j-nums[i]];
}
}
}
return dp[target];
}
- 换硬币
给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.
int coinChange(vector<int> &coins, int amount) {
if(amount==0)
{
return 0;
}
vector<int>dp(amount+1,INT_MAX);
dp[0]=0;
for(int j=0;j<coins.size();j++)
{
for(int i=1;i<=amount;i++)
{
if(i>=coins[j]&&dp[i-coins[j]]!=INT_MAX)
{
dp[i]=min(dp[i],dp[i-coins[j]]+1);
}
}
}
if(dp[amount]==INT_MAX)
{
return -1;
}
else
{
return dp[amount];
}
}
- 约翰的后花园
约翰想在他家后面的空地上建一个后花园,现在有两种砖,一种3 dm的高度,7 dm的高度。约翰想围成x dm的墙。如果约翰能做到,输出YES,否则输出NO。
string isBuild(int x) {
// write you code here
if(x<3||(x>3&&x<6))
{
return "NO";
}
if(x==6||x==3||x==7)
{
return "YES";
}
vector<int>dp(x+1,0);
vector<int>sub{3,7};
dp[3]=3;
dp[4]=3;
dp[5]=3;
dp[6]=6;
dp[7]=7;
for(int i=8;i<x+1;i++)
{
if(i%3==0||i%7==0)
{
dp[i]=i;
}
else
{
for(int j=0;j<sub.size();j++)
{
dp[i]=max(dp[i],dp[i-sub[j]]+sub[j]);
}
}
}
if(dp[x]==x)
{
return "YES";
}
else
{
return "NO";
}
}
后花园这个题有点意思;
这个题解是个玩笑。。。这里面其实有逻辑漏洞,但还是AC掉了。。。
for (int i = 0; i < 2; i++) {
for (int j = heights[i]; j <= x; j++) {
dp[j] = dp[j] || dp[j - heights[i]];
// 本来就可以被围成 VS 之前可以被围成且加上当前这块砖可以被围成(扣去当前砖之前可以被围成)
}
}
return dp[x] ? "YES" : "NO";
这个算是正解。
三.可分割可重复的背包问题
- 杆子分割
给一个 n 英寸长的杆子和一个包含所有小于 n 的尺寸的价格. 确定通过切割杆并销售碎片可获得的最大值.
int cutting(vector<int> &prices, int n) {
// Write your code here
vector<int>dp(n+1,0);
for(int j=0;j<prices.size();j++)
{
for(int i=1;i<n+1;i++)
{
if(j+1<=i)
{
dp[i]=max(dp[i],dp[i-j-1]+prices[j]);
}
}
}
return dp[n];
}
咳咳咳,可分割可重复和不可分割可重复其实解法一样啦。
四.乱入的动态规划
91. 最小调整代价
给一个整数数组,调整每个数的大小,使得相邻的两个数的差不大于一个给定的整数target,调整每个数的代价为调整前后的差的绝对值,求调整代价之和最小是多少。
我感觉这道题是乱入背包问题的动态规划题,当然啦,不得不承认是有关联性的,那就一起整了吧!
int MinAdjustmentCost(vector<int> &A, int target) {
// write your code here
vector<vector<int>>dp(A.size(),vector<int>(101,INT_MAX));
for(int i=1;i<101;i++)
{
dp[0][i]=abs(i-A[0]);
}
for(int i=1;i<A.size();i++)
{
for(int j=1;j<101;j++)
{
for(int k=1;k<=100;k++)
{
if(target>=abs(j-k))
{
dp[i][j]=min(dp[i][j],dp[i-1][k]+abs(j-A[i]));
}
}
}
}
int res=INT_MAX;
for(int i=1;i<101;i++)
{
res=min(res,dp[A.size()-1][i]);
}
return res;
}
dp[i][j]的意义是,数组中前i个数字进行调整的最小代价,j=nums[i-1]。
结束!