投资分配问题
(股票投资问题)
背包问题(工厂排放问题、装载问题)
1、0/1背包
下面参考视频:https://www.bilibili.com/video/BV1K4411X766
看完壁咚
博客参考:https://blog.csdn.net/qq_37767455/article/details/99086678
0/1背包问题代码(就是上面的放和不放):
// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
int dp[][]=new int[N+1][W+1]; //dp[i][j]代表放入前i个,在j重量下最大价值
for (int i = 1; i <=N; i++) {
for (int j = 1; j <=W; j++) {
if (j<weights[i-1]){
dp[i][j]=dp[i-1][j];
}else {
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weights[i-1]]+values[i-1]);
}
}
}
return dp[N][W];
}
对其进行优化,使他变为一维dp,但是此时要逆序,因为如果是正序的话,dp[j]=Math.max(dp[j],dp[j-w[i]+v[i]])每次使用的是旁边的值(dp[j]) 【上一行的数据会被覆盖掉】,而不是上一行旁边的值。还是不懂可以试试这个:
正序会使用同一行数据,而不是上一行。
这里滚动只保留最后一行而已
优化的01背包(重点,理解完全背包的重点):
// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
int dp[]=new int[W+1];
for (int i = 1; i <=N; i++) {
for (int j = W; j >=1; j--) {
if (j>=weights[i-1]){
//这里第二个dp[j]是上一行的(类似之前的dp[i-1][j]),赋值完dp[j]才为当前行
//反正它就是只保留最新一行,那么最大就在最后面的那个元素。
dp[j]=Math.max(dp[j],dp[j-weights[i-1]]+values[i-1]);
}
}
}
return dp[W];
}
上述正序试错:
我好像懂了为什么需要逆序了:因为类似dp[i]=dp[i-1]+…,因为如果我们从前往后的话,dp【i-1】就不是上一行的了
1、(想了半天终于明白了为什么一维动态规划要逆序了。。。我也不知道自己说的好不好对不对。 dp[j] = dp[j] | dp[j - nums[i]] 实际上是用的 i - 1 层 dp[j] 和 dp[j - nums[i]] 得出的。 因此,如果顺序遍历的话, dp[j - num[i]] 会首先被更新成新的值, 然后再算 dp[j] = dp[j] | dp[j - nums[i]] 就不对了, 所以要逆序遍历。 emm 也不知道 讲的对不对。)
2、压缩到一维时,要采用逆序。dp[j] = dp[j] || dp[j - nums[i]] 可以理解为 dp[j] (新)= dp[j] (旧) || dp[j - nums[i]] (旧),如果采用正序的话 dp[j - nums[i]]会被之前的操作更新为新值
真的懂了:因为i每加1代表新的一行开始,由于dp[j-num[i]]每次都得使用的是上一行的数据。但是如果你正序的话,那么你在计算dp[j]的时候用到的dp[j-num[i]]是本行的,而不是上一行的,所以用逆序,逆序用到的dp[j-num[i]]是上一行的。[dp[j-w[i-1]也要是上一行的才行]]
2、完全背包
完全背包朴素方法,在0/1背包问题的优化版再添加多一个for循环,j/w[i]:代表的是当前容量下最多能放下第i件的最大数量:
但时间复杂度太高,我们使用下面优化版的:
完全背包如果不拿那么值就从上一行来,如果拿了是从本行来的
代码与01背包优化版的差不多,只不过从正序而已:
// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
int dp[]=new int[W+1];
for (int i = 1; i <=N; i++) {
for (int j = 1; j <=W; j--) {
if (j>=weights[i-1]){
//这里用正序的,数据从本行来的
dp[j]=Math.max(dp[j],dp[j-weights[i-1]]+values[i-1]);
}
}
}
return dp[W];
}
力扣凑硬币322题,就是用了完全背包的思想
//类比完全背包可解,只不过它要求求出所以return,而且又点条件,所以fill,而且最小所以得min
// W 为背包总体积 金额
// N 为物品数量 coints[].length
// weights 数组存储 N 个物品的重量 每个硬币大小
// values 数组存储 N 个物品的价值 都为1
public static int coinChange(int[] coins, int amount) {// [1,2,5] 11
int max = amount + 1; //max = 12,判断用的。共有amount个硬币,共有amount+1个状态,amount+1个金额,max也是为最后判断是否可以兑换立个条件,因为fill每个dp[]为max那么它就一定大于amount了,就返回-1
int[] dp = new int[amount + 1]; // dp长度12
Arrays.fill(dp, max); // 填充12 //为了找不出就为最大的 必须将所有的dp赋最大值,因为要找最小值
dp[0] = 0; // 0 无法兑换出,有用的不能设置为12,比如1块在容量为1的话为dp[1-w[i]]=dp[0]+1=1不可能等于13
//这里dp【i】的含义是在当前金额下,能够凑出来,硬币数(价值)最小
for (int i = 0; i < coins.length; i++) {
for (int j = 0; j <= amount; j++) {
if (j >= coins[i]) {
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];// -1时无法兑换,可以用[3,5] 7来验证
}