0-1背包与完全背包
0-1背包问题指的是n件物品要么选中要么不选,计算选中的物品的价值
完全背包问题则每件物品可以多次选择
[leetcode-416]分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
if(len < 2) return false;
int sum = 0;
for(int i = 0; i < len; i++){
sum += nums[i];
}
if((sum & 1) == 1) return false;
int target = sum / 2;
boolean[][] dp = new boolean[len][target + 1];
//初始化dp数组
dp[0][nums[0]] = true;
//这里是合理的,表示当给定的数正好是target时,满足条件
for(int i = 0; i < len; i++){
dp[i][0] = true;
}
for(int i = 1; i < len; i++){
for(int j = 1; j <= target; j++){
if(j - nums[i] >= 0)
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
return dp[len - 1][target];
}
}
[leetcode-494]目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。
class Solution {
public int findTargetSumWays(int[] nums, int S) {
int len = nums.length;
int[][] dp = new int[len][2001];
//注意使用“+”,这里统计的是个数,达到当前目标值方案的个数
dp[0][1000 - nums[0]] = 1; dp[0][1000 + nums[0]] += 1;
for(int i = 1; i < len; i++){
for(int j = -1000; j < 1001; j++){
//能达到当前值的前提是不包括当前元素方案的个数大于0.
if(dp[i - 1][j + 1000] > 0){
dp[i][j + 1000 + nums[i]] += dp[i - 1][j + 1000];
dp[i][j + 1000 - nums[i]] += dp[i - 1][j + 1000];
}
}
}
//注意对S进行判断,因为约束了target范围是[-1000, 1000]
return S > 1000 ? 0 : dp[len - 1][S + 1000];
}
}
[leetcode-474]一和零
本题体现了双重背包--字符串为物品,两个背包分别是0和1的个数。本质上仍然是0-1背包问题
在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
注意:
给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4
解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
int[][][] dp = new int[len + 1][m + 1][n + 1];
for(int i = 1; i <= len; i++){
int[] cnt = count(strs[i - 1]);
for(int j = 0; j <= m; j++){
for(int k = 0; k <= n; k++){
dp[i][j][k] = dp[i - 1][j][k];
if(j - cnt[0] >= 0 && k - cnt[1] >= 0)
dp[i][j][k] = Math.max(dp[i][j][k], dp[i - 1][j - cnt[0]][k - cnt[1]] + 1);
}
}
}
return dp[len][m][n];
}
public int[] count(String s){
int len = s.length();
int[] res = new int[2];
for(int i = 0; i < len; i++){
res[s.charAt(i) - '0']++;
}
return res;
}
}
[leetcode-377]组合总和 Ⅳ
每个元素可重复选取这点类似完全背包,但不同的是不同的顺序也要记录这一点
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
nums = [1, 2, 3]
target = 4
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
因此输出为 7。
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
//这里设置成1是合理的,指的是当n==target时符合要求
dp[0] = 1;
for(int j = 1; j <= target; j++){
for(int n : nums){
if(j - n >= 0)
dp[j] += dp[j - n];
}
}
return dp[target];
}
}
[leetcode-322]零钱兑换
完全背包问题
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
class Solution {
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 >= 0)
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
if(dp[amount] == amount + 1){
dp[amount] = -1;
}
return dp[amount];
}
}
[leetcode-518]零钱兑换II
原文: link.
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
class Solution {
public int change(int amount, int[] coins) {
int len = coins.length;
//coins数组为空时,根据amount分为两种情况
if(len == 0){
if(amount == 0) return 1;
return 0;
}
int[][] dp = new int[len][amount + 1];
for(int i = 0; i <= amount; i += coins[0]){
dp[0][i] = 1;
}
for(int i = 1; i < len; i++){
for(int j = 0; j <= amount; j++){
dp[i][j] = dp[i - 1][j];
if(j - coins[i] >= 0)
dp[i][j] += dp[i][j - coins[i]];
}
}
return dp[len - 1][amount];
}
}