背包问题 LeetCode
0-1背包问题
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大。
使用一维数组更为简洁。
0-1背包和完全背包唯一不同就是体现在遍历顺序上,从一维数组看,第二个遍历过程0-1是从后往前,完全背包是从前往后
package leetcode;
import java.util.Arrays;
public class BagDemo {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
// 物品的数量
int num = weight.length;
// 背包容量
int vol = 4;
int[][] dp = new int[num][vol + 1];
// 初始化dp数组, 第一行从背包容量等于物品质量开始,剪枝
for (int j = weight[0]; j <= vol; j++) {
dp[0][j] = value[0];
}
// 遍历行,即先遍历物品,数量从小到多
for (int i = 1; i < num; i++) {
// 再遍历列,即遍历容量,从小到大
for (int j = 1; j <= vol; j++) {
// 如果当前背包容量小于当前物品重量,无法放入,
// 最大值等于不放这个物品的最大值:
if (j < weight[i]) {
// [i - 1]等于不放这个物品的重量
dp[i][j] = dp[i - 1][j];
} else {
// 如果放进去:相当于容量为j - i的背包的dp值加上当前i的价值;
int isPut = dp[i - 1][j - weight[i]] + value[i];
int noPut = dp[i - 1][j];
// 两种情况取最大值
dp[i][j] = Math.max(isPut, noPut);
}
}
}
System.out.println(dp[num - 1][vol]);
System.out.println(Arrays.deepToString(dp));
}
}
class BagDemo1 {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
// 物品的数量
int num = weight.length;
// 背包容量
int vol = 4;
int[] dp = new int[vol + 1];
for (int i = 0; i < num; i++) {
// 从后向前,前面的数字不会受影响;
for (int j = vol; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
System.out.println(Arrays.toString(dp));
}
}
}
LeetCode题解
/**
* 139. 单词拆分
*/
// 自己写出来的复杂度比官方解答小,逻辑更细节一些,还是有点蒙
class Solution99 {
public boolean wordBreak(String s, List<String> wordDict) {
String[] word = wordDict.toArray(new String[0]);
char[] str = s.toCharArray();
boolean[] dp = new boolean[str.length + 1];
dp[0] = true;
// 字符串组合是无序的,所以要遍历背包容量,再遍历物品
for (int j = 0; j <= str.length; j++) {
for (String w : word) {
// 边界确实有点懵逼,left和right
if (j >= w.length() && isTrue(str, w, j - w.length(), j))
dp[j] |= dp[j - w.length()];
}
}
return dp[str.length];
}
public boolean isTrue(char[] str, String word, int left, int right) {
int j = 0;
for (int i = left; i < right; i++, j++) {
if (str[i] != word.charAt(j)) return false;
}
return true;
}
}
/**
* 279. 完全平方数
*/
class Solution98 {
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, n + 1);
dp[0] = 0;
for (int i = 0; i * i <= n; i++) {
for (int j = i * i; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
}
/**
* 322. 零钱兑换
*/
class Solution97 {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
int maxValue = amount + 1;
Arrays.fill(dp, maxValue);
dp[0] = 0;
for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
return dp[amount] != maxValue ? dp[amount] : -1;
}
}
class Solution96 {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int j = 1; j <= amount; j++) {
for (int coin : coins) {
if (j >= coin) {
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
}
return dp[amount] > amount ? dp[amount] : -1;
}
}
/**
* 70. 爬楼梯 (进阶)
* 完全背包问题
*
* @date 2021 11 15
*/
class Solution95 {
public int climbStairs(int n) {
int[] value = new int[]{1, 2};
int[] dp = new int[n + 1];
dp[0] = 1;
for (int i = 0; i <= n; i++) {
for (int v : value) {
if (i >= v) {
dp[i] += dp[i - v];
}
}
}
return dp[n];
}
}
/**
* 377. 组合总和 Ⅳ
*
* @date 2021 11 14
*/
class Solution94 {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int j = 0; j <= target; j++) {
for (int num : nums) {
if (num <= j) {
dp[j] += dp[j - num];
}
}
}
return dp[target];
}
}
/**
* 518. 零钱兑换 II
*/
class Solution93 {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] += dp[j - coin];
}
}
return dp[amount];
}
}
/**
* 474. 一和零
*/
// 二维背包问题,唉,懵逼
class Solution92 {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
// 初始值,相当于什么都不拿,价值为0,可以忽略不写;
for (String str : strs) {
// 找出当前元素的0,1个数,相当于找出当前物品的质量
int sum0 = 0, sum1 = 0;
for (char c : str.toCharArray()) {
if (c == '0') {
sum0++;
} else sum1++;
}
// 二维背包的重量,两重for循环遍历取得最大值
for (int i = m; i >= sum0; i--) {
for (int j = n; j >= sum1; j--) {
// 当前物品的价值为一;
dp[i][j] = Math.max(dp[i - sum0][j - sum1] + 1, dp[i][j]);
}
}
}
return dp[m][n];
}
}
/**
* 494. 目标和
*/
class Solution91 {
public int findTargetSumWays(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
if (Math.abs(target) > sum || (sum + target) % 2 != 0) return 0;
int vol = (sum + target) / 2;
int[] dp = new int[vol + 1];
// dp的值代表着方法总数。初始值为1
dp[0] = 1;
for (int num : nums) {
for (int j = vol; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[vol];
}
}
/**
* 1049. 最后一块石头的重量 II
* 2021 11 13
*/
// 使用boolean数组,速度更快
// dp[j] = true 此时的j即为能取到的最大值
class Solution90 {
public int lastStoneWeightII(int[] stones) {
int sum = Arrays.stream(stones).sum();
int vol = sum / 2;
int[] dp = new int[vol + 1];
for (int stone : stones) {
for (int j = vol; j >= stone; j--) {
dp[j] = Math.max(dp[j], dp[j - stone] + stone);
}
}
return sum - dp[vol];
}
}
/**
* 416. 分割等和子集
*/
// 官方解答,使用boolean数组
class Solution89 {
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) {
return false;
}
int sum = 0, maxNum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; --j) {
dp[j] |= dp[j - num];
}
}
return dp[target];
}
}
// 巧妙的转化成背包问题
class Solution88 {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 != 0) {
return false;
}
// 限定背包容量,所得的子集和只会小于等于sum / 2;
int vol = sum / 2;
int[] dp = new int[vol + 1];
for (int i = 0; i < nums.length; i++) {
for (int j = vol; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[vol] == vol;
}
}