视频讲解:
带你学透0-1背包问题!| 关于背包问题,你不清楚的地方,这里都讲了!| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili
01背包问题
思路:总结一下自己的思路体会:
- 01背包问题的维度分别是物品数量,物品的特征体现(如重量);
- 01背包问题的递归公式从前i-1种物品的考虑,然后考虑当前的第i种物品,其实这和我们前面所做的双指针类型的题目一样,都是对于数组开始从无到有的一个个开始遍历的,所有前面已经遍历完成的一定包含了前面的元素所有可行的最佳情况,所以动态规划本质上也是一种贪心;
- 关于状态转换,我觉得最难以理解的就是,如果当前物品放不进去,就是直接继承上一层的同列元素,这是我认为01背包中最难以理解的部分,也是我目前使用说"前i-1种操作都包含在dp[i-1][j]",所以当前放不进去的话就可以直接继承下来,我暂时还说服自己。
- 最后想要学习的一点就是01背包考虑的是怎么性价比最大的放入元素。
- 第二点是重量维度进行遍历时需要从后往前遍历,可以减少某些小重量物品的重复加入!!!那么反过来想,支持重复加入的从前遍历,是否就是完全背包问题的解法?
java
// 时间复杂度O(n^2)
// 空间复杂度O(n)或O(n^2)
import java.util.*;
public class runafter {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int M = scanner.nextInt();
int N = scanner.nextInt();
int[] weights = new int[M];
int[] values = new int[M];
for(int i=0; i<M; i++)
weights[i] = scanner.nextInt();
for(int i=0; i<M; i++)
values[i] = scanner.nextInt();
// 定义dp数组,数组的下标表示的是在容量N下物品i+1的所有组合情况,比如重量为1,可以单放;重量为3了,我和物品j一起放了进去,所以每次遍历时是仅仅考虑当前物品的重量与价值,而出现超过当前重量的内容,可以再组合一些其他的,也是一种纯暴力的思路
// 数组的值表示的是第i+1个物品操作后前i+1个共取得的最大价值
int[][] dp = new int[M][N+1];
for(int i=1; i<=N; i++)
if(i>=weights[0])
dp[0][i] = values[0];
// for (int i=0; i<M; i++)
// dp[i][0] = 0;
for (int i=1; i<M; i++){
for(int j=1; j<=N; j++){
// j表示当前背包的容量
if(j>=weights[i]){
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i]]+values[i]);
}
else
dp[i][j] = dp[i-1][j];
}
}
System.out.println(dp[M-1][N]);
int[] DP = new int[N+1];
for(int i=0; i<M; i++){ // 物品个数
for(int j=N; j>=1; j--){ // 重量
if(j >= weights[i])
DP[j] = Math.max(DP[j], DP[j - weights[i]] + values[i]);
}
}
System.out.println(DP[N]);
return;
}
}
416. 分割等和子集
思路:在拿到这道题目时,其实第一想法是回溯,去不断试错当前的子集和;但回溯的时间复杂度必定是指数级,大概率会超时。其次想到贪心,走动态规划,因为这是一个不断加入元素尝试的过程,所以是动规。那么再看分割子集,其实也是一个不断加入元素从一个集合至另一个集合的过程,所以可以视作背包问题,并且每个元素可以取一次,所以是01背包问题,下面需要处理的就是动态规划,我们的目标是操作数组和,所以和就是本题所理解的重量,以物品数和数组和作为两个维度构建数据即可求解。
java
class Solution {
public boolean canPartition(int[] nums) {
if(nums.length == 1)
return false;
int m = nums.length;
int n = 0;
for(int i=0; i<m; i++)
n += nums[i];
if(n%2 == 1)
return false;
// if(nums[0] == n/2)
// return true;
// int[][] dp = new int[m][n+1];
// // 初始化
// for(int j=1; j<=n; j++)
// if(j >= nums[0]){
// dp[0][j] = nums[0];
// }
// for(int i=1; i<m; i++){
// for(int j=n; j>=1; j--){
// if(j>=nums[i]){
// dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i]);
// }
// else
// dp[i][j] = dp[i-1][j];
// if(dp[i][j] == n/2)
// return true;
// }
// }
int[] dp = new int[n/2+1];
for(int i=0; i<m; i++){ // 数字的编号
for(int j=n/2; j>=1; j--){ // 类似于重量
if(j >= nums[i])
dp[j] = Math.max(dp[j], dp[j-nums[i]]+nums[i]);
if(dp[j] == n/2)
return true;
}
}
return false;
}
}