01背包问题
有n个重量为w1,w2,w3…的物品,价值分别为v1,v2,v3…,现有一个容量为C的背包,问在不超过背包容量的条件下,所装物品的最大价值是多少?
这个问题有两个变量,分别为物品总数n,背包容量C。记
F
(
n
,
C
)
F(n,C)
F(n,C)表示n件物品,C容量时的最大价值。考虑最后一步,
F
(
n
)
F(n)
F(n)能通过
F
(
n
−
1
)
F(n-1)
F(n−1)放与不放当前物品推导出来。
不放当前物品:
F
(
n
,
C
)
=
F
(
n
−
1
,
C
)
F(n,C)=F(n-1,C)
F(n,C)=F(n−1,C)
放当前物品:
F
(
n
,
C
)
=
m
a
x
{
F
(
n
−
1
,
C
)
,
v
[
n
]
+
F
(
n
−
1
,
C
−
w
[
n
]
)
}
F(n,C)=max\{F(n-1,C), v[n]+F(n-1,C-w[n])\}
F(n,C)=max{F(n−1,C),v[n]+F(n−1,C−w[n])}
这并不是贪心算法,因为我们计算了 F ( n , C ) F(n,C) F(n,C)的所有可能情况,并取了最大值,而贪心只取最后一步的最大值。
递归方法
int zero_one_bag(vector<int>& w, vector<int>& v, int index, int cap)
{
if (cap <= 0 || index == w.size())
return 0;
int res = zero_one_bag(w, v, index + 1, cap);
if (w[index] <= cap)
res = max(res,
zero_one_bag(w, v, index + 1, cap - w[index]) + v[index]);
return res;
}
记录状态值(动态规划)
因为递归是不记录状态值的,所以会产生重复计算,因此把状态记录在数组里,如果状态值已经计算过,就直接返回。
其实这已经是动态规划。常见的动态规划是循环,这里递归改成循环就可以了。
空间复杂度
O
(
n
∗
C
)
O(n*C)
O(n∗C)
vector<vector<int>> dp(w.size(),vector<int>(5 + 1,0));
int zero_one_bag_1(vector<int>& w, vector<int>& v, int index, int cap, vector<vector<int>>& dp)
{
if (cap <= 0 || index <0)
return 0;
if (dp[index][cap] > 0) return dp[index][cap];
int res = zero_one_bag_1(w, v, index - 1, cap, dp);
if (w[index] <= cap)
res = max(res,
zero_one_bag_1(w, v, index - 1, cap - w[index], dp) + v[index]);
return res;
}
空间复杂度 O ( C ) O(C) O(C)
之前的方法使用二维数组做额外空间,其实
d
p
(
n
)
dp(n)
dp(n)只与
d
p
(
n
−
1
)
dp(n-1)
dp(n−1)有关,所以只用长度为C+1的一维数组保存n-1时的状态int dp[C+1]
。因为计算
d
p
[
i
]
[
j
]
=
m
a
x
{
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
}
dp[i][j]=max\{dp[i-1][j], dp[i-1][j-w[i]]+v[i]\}
dp[i][j]=max{dp[i−1][j],dp[i−1][j−w[i]]+v[i]}需要用到数组前半部分的值,如果j从前向后计算会覆盖掉n-1时的值,所以从后向前计算。
int zero_one_bag_dp(vector<int>& w, vector<int>& v, int C)
{
vector<int> dp(C + 1, 0);
for (int i = 0; i < w.size(); i++)
{//j >= w[i];是因为j< w[i]时,i的dp[j] = i-1的dp[j],也就是数组原来的值,不需要计算
for (int j = C; j >= w[i]; j--)
{
dp[j] = max(dp[j], v[i] + dp[j - w[i]]);
}
}
return dp.back();
}
计算 d p [ i ] [ j ] dp[i][j] dp[i][j]时一般逐行计算,可以优化掉i维度。如果 d p [ i , j ] − > d p [ i − 1 , j − k ] dp[i,j]->dp[i-1,j-k] dp[i,j]−>dp[i−1,j−k]表示依赖于上一次(i-1)的前k位置(j-k),需要从后往前计算,避免覆盖,如背包; d p [ i , j ] − > d p [ i , j − k ] dp[i,j]->dp[i,j-k] dp[i,j]−>dp[i,j−k]表示依赖于当前次的之前k位置,需要覆盖掉上一次的值,所以从前往后计算。但是对于 d p [ i , j ] − > d p [ i − 1 , j − k ] , d p [ i , j − k ] dp[i,j]->dp[i-1,j-k], dp[i,j-k] dp[i,j]−>dp[i−1,j−k],dp[i,j−k]则无法优化。如果不清楚可以计算一下dp数组。
最多路径数 leetcode62
问题描述
从m*n数组的左上角到右下角,问总共有多少条不同的路径?
//dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]
//使用长度为n的一维数组,依赖于上一次j位置(本来就是未赋值之前)
//依赖于当前次,之前1位置,所以覆盖。
int[] dp = new int[n]; //
// 初始化
for(int i = 0; i < n; i++){
dp[i] = 1;
}
// 公式:dp[i] = dp[i-1] + dp[i]
for (int i = 1; i < m; i++) {
// 第 i 行第 0 列的初始值
dp[0] = 1;
for (int j = 1; j < n; j++) {
dp[j] = dp[j-1] + dp[j];
}
}
return dp[n-1];
应用
涉及元素选或不选都可以用01背包问题来解决。
- 相同子集和分割(leetcode 416)
给定一个仅包含正整数的非空数组,确定该数组是否可以分成两部分,要求两部分的和相等.
分析:涉及元素选或不选,尝试用01背包解决。题目为是否存在子序列使其和=sum/2。以元素值作为重量和价值,因为包内元素重量<=sum/2。,所以价值总量<=sum/2;求最大价值,如果最大价值 = =sum/2,也即包内总重量==sum/2,符合条件。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int s = 0;
for (int i : nums) s += i;
if (s % 2) return false;
vector<int> dp(s / 2 + 1, 0);
for (int i = 0; i < nums.size(); i++)
{
for (int j = s / 2; j >= nums[i]; j--)
{
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp.back() == s / 2;
}
};
- 双核cpu任务分配问题。(牛客)
一种双核CPU的两个核能够同时的处理任务,现在有n个已知数据量的任务需要交给CPU处理,假设已知CPU的每个核1秒可以处理1kb,每个核同时只能处理一项任务。n个任务可以按照任意顺序放入CPU进行处理,现在需要设计一个方案让CPU处理完这批任务所需的时间最少,求这个最小的时间。
解析:
完成所有n个任务需要sum时间,总运行时间是固定的,假设第一个cpu处理时间为n1,第二个cpu时间为sum-n1,要使处理时间最小,则n1越来越靠近sum/2.我们以第一个核为背包,每个task运行时间为重量和价值,因为包内重量总是<=sum/2,即包内价值<=sum/2.所以求得包内价值最大值,就能让包内重量趋近于sum/2.
由于并行时间是两核运行的较大值,又因n1<=sum/2,所以返回sum/2- n1。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int t = 0, n;
cin >> n;
vector<int> times(n, 0);
for(int i=0;i<n;i++)
{
cin>>times[i];
times[i]/=1024;
t+=times[i];
}
vector<int> dp(t / 2 + 1, 0);
for (int i = 0; i < times.size(); i++)
{
for (int j = t / 2; j >= times[i]; j--)
{
dp[j] = max(dp[j], dp[j - times[i]] + times[i]);
}
}
cout << 1024 * ( t - dp.back());
return 0;
}