0-1背包逐步推导过程,可以详见:0-1背包问题详解(一步一步超详细) - 百度文库
1、LeetCode相关问题涉及:
322. 零钱兑换 中等 --出现频率67.34%
416. 分割等和子集 中等 -- 出现频率71.6%
2、动态规划--DP
3、LeetCode416--分割等和子集
3-1、 该题如何转移为0-1背包问题
1)目标分割成两个等和子集,既该数组的总和sum/2 可以转换为背包的总容量,因此此题可以转换为两个背包问题,并且要求最后每个背包的容量为sum / 2.
2)由于是等和分割,因此数组总和sum 必须是偶数
3)由于每个数组元素,都要分配到两个背包的其中一个去,因此该数组的最大值,即Max(nums[i]) 必不能大于sum /2 , 否则Max(nums[i])是无法分配到背包中的。
4)这道题不需要考虑价值,因此我们只需要通过一个布尔值矩阵来表示状态转移 矩阵。注意边界条件的处理。
- 因此创建二维数组dp(n行:nums的个数, target+1 列:背包容量sum / 2, 因此要循环到sum / 2 +1 ),dp[i][j] 表示从数组的 [0,i]下标范围内选取若干个正整数(可以是 0 个),是否存在一种选取方案使得被选取的正整数的和等于 j。初始时,dp 中的全部元素都是 false。
注意以下边界情况:
- 如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0 ≤ i < n,都有 dp[i][0]=true。
- 当 i==0 时,只有一个正整数 nums[0] 可以被选取,因此 dp[0][nums[0]]=true。
递推公式(j为背包容量):
if(nums[i] < j){
//此时容量充足,可以选择,也可以不选择
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
}else{
//此时背包容量不够,无法选择
dp[i][j] = dp[i-1][j];
}
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
int max = 0;
int len = nums.length;
for(int i = 0; i< len; i++){
sum += nums[i];
max = Math.max(max, nums[i]);
}
//汇总和一定是偶数
if(sum % 2 != 0)
return false;
int target = sum /2;
//最大值一定比目标背包(sum /2 )小,否则无法分割成等和子集
if(max > target)
return false;
boolean[][] dp = new boolean[len][target+1];
// 初始第0行,此时目标值nums[0]比目标值小,则为true
if(nums[0]<target){
dp[0][nums[0]] = true;
}
for(int i = 1; i< len; i++){
for(int j = 1; j<target+1; j++){
if(nums[i] == j){
dp[i][j] = true;
continue;
}
//递推公式
if(nums[i] < j){
//此时容量充足,可以选择,也可以不选择
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
}else{
//此时背包容量不够,无法选择
dp[i][j] = dp[i-1][j];
}
}
}
return dp[len-1][target];
}
}
动态规划:
时间复杂度:O(n×target), n 是数组的长度,target 是整个数组的元素和的一半
空间复杂度:O(n×target) , 空间复杂度取决于dp 数组, dp数组可以优化成一维数组。
空间复杂度优化,dp二维数组可以优化成一维数组,空间复杂度为O(n), 优化代码如下:
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int i = 0; i < n; i++) {
int num = nums[i];
for (int j = target; j >= num; --j) {
dp[j] |= dp[j - num];
}
}