代码随想录算法训练营Day39|动态规划part03-背包问题

152 阅读5分钟

背包问题

Pasted image 20250226105040.png

01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

暴力解法

每个物品只有两种状态,用or不用。所以暴力解法时间复杂度为O(2n)O(2^n) 暴力解法是指数级别的复杂度,很显然需要优化

动态规划

二维dp数组

用如下数据举例: 背包容量为4,共有3个物品

名称重量价值
物品0110
物品1325
物品2430

既然是动态规划,我们先考虑动态规划的五部曲:

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]指任取0~i号物品放入容量为j的背包里,可达到的最大价值。
  2. 确定递推公式:对于任取0~i号物品放入容量为j的背包,为了有最大价值。如果第i件不放入背包,dp[i][j]就为dp[i-1][j]。如果第i件放入(且可以放入),dp[i][j]等于第i件的价值➕dp[i-1][j-w[i]]。只有这两种情况,所以dp[i][j]是这两个值中间的最大值。
  3. dp数组如何初始化:考虑对dp[i][j]的定义和递推公式,需要初始化的是dp[0][j]。考虑dp数组的定义,如果j>=w[0]dp[0][j]初始化为v[0],否则为0
  4. 确定遍历顺序:从上到下,从左到右遍历
  5. 举例推导dp数组:对上面的例子,推导dp数组如下
010101010
010102535
010102535
一维滚动数组
  1. dp数组和下标的含义:dp[j]就是容量为j的背包里能到的最大价值
  2. 确定递推公式 在二维递推公式的基础上,去掉维度i,就可以得到一个一维递推公式dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); 对其含义进行解读:dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值。
  3. dp数组如何初始化:根据数组的含义dp[0]一定为0,其他的为了不影响递推,也初始化0.可以理解为不放入任何物品的行。
  4. 确定遍历顺序:由于我们把第i-1行复制到了第i行,如果从左到右遍历,会覆盖前一行的左侧数据。所以从右到左遍历,这样dp[j]需要的数据都在左侧,而左侧还是上一行的数据
  5. 举例推导dp数组:同上

kama 46 携带研究材料

题目链接:kamacoder.com/problempage…

文档讲解:programmercarl.com/背包理论基础01背包-…

视频讲解:programmercarl.com/other/gongk…

思路

和上述01背包相同

解法

二维数组版

import java.util.Scanner;

public class Main{

	public static void main(String[] args) {
	
		Scanner scanner = new Scanner(System.in);
		
		int m = scanner.nextInt(); // 物品个数
		int n = scanner.nextInt(); // 背包容量
		int[] weight = new int[m];		
		int[] value = new int[m];				  
		
		for (int i = 0; i < m; i++) {		
			weight[i] = scanner.nextInt();		
		}
		
		for (int i = 0; i < m; i++) {		
			value[i] = scanner.nextInt();		
		}
				  		
		int[][] dp = new int[m][n+1];
		// 初始化dp数组
		
		for (int i = 0; i <= n; i++) {		
			if (i >= weight[0]) {			
				dp[0][i] = value[0];			
			}		
		}
		
		for (int i = 1; i < m; i++) {			
			for (int j = 0; j <= n; j++) {			
				if (weight[i] <= j) {				
					dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);				
				}				
				else {				
					dp[i][j] = dp[i-1][j];				
				}			
			}		
		}		
		System.out.println(dp[m-1][n]);	
	}
}

一维滚动数组版

import java.util.Scanner;

public class Main{

	public static void main(String[] args) {
	
		Scanner scanner = new Scanner(System.in);
		
		int m = scanner.nextInt(); // 物品个数		
		int n = scanner.nextInt(); // 背包容量				  		
		int[] weight = new int[m];		
		int[] value = new int[m];				  
		
		for (int i = 0; i < m; i++) {		
			weight[i] = scanner.nextInt();		
		}
		
		for (int i = 0; i < m; i++) {		
			value[i] = scanner.nextInt();			
		}				  
		
		int[] dp = new int[n+1];
		
		// 初始化dp数组
		
		for (int i = 0; i <= n; i++) {		
			dp[i] = 0;		
		}
		
		for (int i = 0; i < m; i++) {		
			for (int j = n; j >= 0; j--) {			
				if (weight[i] <= j) {				
					dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);				
				}				
				else {				
					dp[j] = dp[j];				
				}			
			}		
		}		
		System.out.println(dp[n]);	
	}
}

LeetCode 416 分割等和子集

题目链接:leetcode.cn/problems/pa…

文档讲解:programmercarl.com/0416.分割等和子集…

视频讲解:www.bilibili.com/video/BV1rt…

思路

本题的本质是:求能否装满容量为sum/2的背包,sum是整个数组的和

考虑一个数字的重量和价值,重量对应容量,所以重量是数字本身;同时需要让背包在sum/2的维度里最大,所以价值也对应数字本身。

所以这个背包问题是,给一个容量为sum/2的背包,装到价值最大,价值是否为sum/2

于是我们把本题转化为了背包问题,考虑动态规划五部曲:

  1. dp数组和下标含义:dp[j]为容量为j的背包能装下的数字的最大和
  2. 确定递推公式:dp[j] = max(dp[j], dp[j-nums[i]]+nums[i])
  3. dp数组如何初始化:一个数字都不放,都初始化为0
  4. 确定遍历顺序:从右向左
  5. 举例推导dp数组:假设数组为[1,5,11,5],每一行代表一次迭代.sum为22,sum/2为11
000000000000
011111111111
011115666666
0111156666611
0111156666611

解法

class Solution {
	public boolean canPartition(int[] nums) {	
		int sum = 0;		
		for (int i = 0; i < nums.length; i++) {		
			sum += nums[i];		
		}		
		if (sum % 2 == 1) {		
			return false;		
		}
		
		int[] dp = new int[sum/2 + 1];		
		for (int i = 0; i < nums.length; i++) {		
			for (int j = sum/2; j >= 0; j--) {			
				if (nums[i] <= j) {				
					dp[j] = Math.max(dp[j], dp[j-nums[i]]+nums[i]);				
				}			
			}		
		}		
		if (dp[sum/2] == sum/2) {		
			return true;		
		}		
		return false;	
	}
}

今日收获总结

解决背包问题,需要搞清楚价值和容量在题中的含义,然后用动态规划五部曲解决