这周主要是做动态规划的题。背包问题看的是知乎上SMON的 《动态规划之背包问题系列》。
两个背包问题模板
01背包
//
int n;// 物品数量
vector<int> w(n);//物品重量
vector<int> v(n);//物品价格
int capacity;
vector<int> dp(capacity+1,0);
for(int i = 1; i<= n; i++)
{
for(int j = capacity; j>=w[i-1]; j--)
{
dp[j] = max(dp[j],dp[j-w[i] + v[i-1]);
}
}
完全背包
int n;
vector<int> w(n);
vector<int> v(n);
int capacity;
vector<int> dp(capacity+1);
for(int i = 1; i<= n;i++)
{
for(int j = w[i-1]; j <= capacity;j++)
{
dp[j] = max(dp[j], dp[j-w[i-1]]+ v[i])
}
}
其实这个dp看起来像是状态,从考虑第1个物品,到考虑第二个物品,直到最后考虑第n个物品。
leetcode上的题目
这里都是直接抄的 SMON的答案,虽然没有自己想,但是能帮助我理解模板。
leetcode 416 分割等和子集
bool canPartition(vector<int>& nums)
{
int sum = 0, n = nums.size();
for(int & num:nums) sum+= num;
if(sum%2)return false;
int capacity = sum>>1;
vector<bool> dp(capacity +1,false);
dp[0] = true;
for(int i = 1; i <= nums.size(); i++)
{
for(int j = capacity; j>= nums[i-1]; j--)
{
dp[j] = dp[j]||dp[j-nums[i-1]];
}
}
return dp[capacity];
}
这里的dp其实可以理解为前i个数字是否能够正好装满 j。
leetcode 322 零钱兑换
完全背包
int coinChange(vector<int>& coins, int amount)
{
vector<int>& dp(amount +1, INT_MAX);//此时求最小
dp[0] = 0; //0块前最少用0个
for(int i = 1; i <= coins.size(); i++)
{
for(int j = coins[i]; j<= amount; j++)
{
if(dp[j] > dp[j-coins[i-1]]+1)
{
dp[j] = 1 + dp[j - coins[i]];
}
}
}
return dp[amount]==INT_MAX?-1:dp[amount];
}
leetcode 494 目标和
这个问题将一个表达式构造问题转化成为背包问题。所有的取+的元素和为A,取 - 的元素和为B,则target = A - B, sum = A+B。则A= (target + sum)/2。我们仅仅需要找到元素和为A的就可以了。
此时的dp[j]为前i个数字满足目标j的方案数。
dp[0] : 前0个物品满足数字0的方案数:1 种 。
dp[j] = dp[j] + dp[j-nums[i-1]]
如果第i个物品不选是dp[j] ,如果选择第i个物品 ,则有dp[j-nums[i-1]]种选择。
int findTargetSumWays(vector<int>& nums, int target)
{
int sum = 0;
sum = accumulate(nums.begin(), nums.end(), 0);
if(target >sum|| target < -sum) return false;
if((sum+ target)&1)return false;
int S= (sum + target)>>1;
vector<int> dp(S+1, 0);
dp[0] =1;//前0个数字组成和为0 有一种 就是 全都不取
//注意取决策数量的时候dp[0]应该为1
for(int i =1; i <= nums.size(); i++)
{
for(int j = S; j>= nums[i-1]; j--)
{
dp[j] = dp[j] + dp[j-nums[i-1]];
}
}
return dp[S];
}
leetcode 474一和零
二维背包,更能感觉dp其实是一个随着i变化的状态矩阵。
int findMaxForm(vector<string>& str. int m, int n)
{
int num = strs.size();
int w0, w1;
vector<vector<int>>dp(m+1,vector<int>(n+1, 0));
for(int i = 1; i<= nums.size(); i++)
{
//计算0 的数量与1的数量
w0 = 0,w1 = 0;
for(char& c :nums[i-1])
{
if(c == '0') w0++
else w1++;
}
for(int j = m; j>= w0; j--)
{
for(int k = n; k>= w1; k--)
{
dp[j][k] = max(dp[j][k], dp[j-w0][k-w1]+1);
}
}
}
return dp[m][n];
}
一个动态规划与分治集合的题目
周2做了一个leetcode312 戳气球,根据下面赞同数最高的题解思路写的。
这个题解的分治思路是:
dp[i][j] = 开区间内获得硬币的最大数量。
对于开区间 (i,j),开区间意味着i和j不会被戳破。
设i<k <j 是这个区间内最后一个戳破的气球,
则 有
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
k
]
+
d
p
[
k
]
[
j
]
+
n
u
m
s
[
i
]
∗
n
u
m
s
[
k
]
∗
n
u
m
s
[
j
]
)
i
<
k
<
j
dp[i][j] = max(dp[i][k] + dp[k][j] + nums[i]* nums[k]*nums[j]) \quad i<k<j
dp[i][j]=max(dp[i][k]+dp[k][j]+nums[i]∗nums[k]∗nums[j])i<k<j
int maxCoins(vector<int>& nums)
{
int n = nums.size();
if(n == 1)
{
return nums[0];
}
nums.resize(n+2);
for(int i = n-1; i>=0; i--)
{
nums[i+1] = nums[i];
}
nums[0] = 1,nums[n+1] = 1;
int total = 0;
for(int t = 2; t<= n+1; t++)
{
for(int i = 0; i<= n+1; i++)
{
total = 0;
for(int k = i+1; k<= i+t; k++)
{
total = max(total, dp[i][k]*dp[k][i+t]*nums[i]*num[k]*nums[i+k] );
}
}
}
return dp[0][n+1];
}
但是开这么大的矩阵空间确实太浪费了,应该可以改进一下。