416.分割等和子集
题目链接:416. 分割等和子集 - 力扣(LeetCode)
解题思路:
首先使用背包的思想去模拟这道题,想清楚背包的容量以及要计算的最大价值是什么,这个题目可以理解为物品的价值和物品的重量是相等的。想要分割出相等的两个子集,那么只需要找出那么一组数字,能够将所有数字的和sum/2大小的背包正好装满,即可证明能够将数组分割成相等的两部分。
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i : nums){
sum += i;
}
if(sum % 2 == 1) return false;
sum = sum / 2;
/**
* 1.确定dp数组及其下标含义 dp[i][j] 从下标0-i数字当中选取的和不超过j的最大和
* 2.dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i])
* 3.dp[i][0] = 0 dp[0][j] if(j < nums[0]) = 0 else = nums[0]
* 正向循环
*/
int[][] dp = new int[nums.length + 1][sum + 1];
for(int i = 0; i < nums.length; i++){
dp[i][0] = 0;
}
for(int i = 0; i <= sum; i++){
if(i > nums[0]){
dp[0][i] = nums[0];
}
}
for(int i = 1; i < nums.length; i++){ // 外层循环物品
for(int j = 1; j <= sum; 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[nums.length - 1][sum] == sum) return true;
return false;
}
}
思考:
当使用二维数组进行递推的时候,有两个问题并不需要考虑,一个是循环的内外层顺序,一个是循环的方向。因为前面已经确定下来的值,并不会影响后续的值。
但是当使用一维数组进行递推的时候,情况就不一样了。内层的背包容量循环需要考虑到循环的方向问题,是正向循环还是反向循环,答案是反向循环,因为当前的递推会使用到前面的以前二维的上一层的值,如果正向循环,前面的值还没等后面的同一层的值使用,就已经被更新为新一层的内容了,这肯定是不行的,所以要从后向前循环。
同时对代码中for循环内嵌套if的语句进行了调整优化。
代码优化:
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i : nums){
sum += i;
}
if(sum % 2 == 1) return false;
sum = sum / 2;
/**
* 1.确定dp数组及其下标含义 dp[j] 背包容量为j时,背包能够装的最大重量
* 2.dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i])
* 3.dp[0] = 0 dp[j] if(j < nums[0]) = 0 else = nums[0]
* 正向循环
*/
int[] dp = new int[sum + 1];
for(int j = nums[0]; j <= sum; j++){
dp[j] = nums[0];
}
for(int i = 1; i < nums.length; i++){ // 外层循环物品
for(int j = sum; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if(dp[sum] == sum) return true;
return false;
}
}