HOT100题打卡第26天——动态规划
中午好,继续来写今天的题
上题目
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
分析题目
解题思路
将数组分成两个子集,子集的元素和相等,其实我们就可以理解为,每个子集的元素和都等于数组的元素和除以2
我们把子集的元素和用target表示,数组的元素和用sum表示,那么target=sum/2
因为两个子集的元素和是相等的,因此只需要判断能否凑成一个和为target的子数组,一个满足另一个也一定满足
但是如果sum是奇数,那就不可能分成两个元素和相等的子数组
dp数组含义
这道题用到的是boolean型数组
数组下标索引表示当前需要凑的目标值,dp[ i ]就表示能否凑成和为 i 的子数组
只需要判断到target,所以dp数组长度=target+1(target就是下标i,加1是因为还有0索引)
数组初始化
因为一开始并不确定是否能凑成和为 i 的子数组,所以我们一开始把dp数组中的每一个元素的值都初始化为false
然后因为和为0的子数组始终能凑成,就是空集,所以dp[0]=true;
循环遍历
首先我们是从nums数组中获取元素来凑成目标值,所以先遍历nums数组
内层循环遍历的是dp数组,因为目标值是targe(dp数组中索引=目标值),所以最大只需遍历到target,
状态转移逻辑:
若不选当前元素nums[i]时,dp[j]已经为true(能凑出j),则保持true;
若选当前当前元素nums[i]时,之前能凑出j - nums[i](即dp[j - nums[i]]为true)
则加上当前元素后就能凑出j,因此dp[j]更新为true。
内层为什么反向遍历
用例一来解释:
总和 sum=22,target=11,dp 数组索引 0~11(dp[j] 表示能否凑出和为 j 的子集)。
初始 dp = [T, F, F, F, F, F, F, F, F, F, F, F](仅 dp[0]=T,空集)。
如果是正序遍历(j从1到11)
逐个更新 j=1→2→...→11,每步用当前更新后的 dp 状态:
j=1:dp[1] = F || dp[0](T)→ T(正确,用 1 凑 1)→ dp[1]=T
j=2:dp[2] = F || dp[1](刚更的T)→ T(错误!此时只有 1 个 1,却凑出 2,相当于用了两次 1)
j=3:dp[3] = F || dp[2](刚更的T)→ T(错误,1+1+1=3,用了三次 1)
... 以此类推,j=4——11 都会被错误更新为 T(因为每次都用前一步更新的小 j)
因此需要倒序遍历,因为dp数组初始值都是false,倒着遍历时不会受到前面结果的影响
ok,分析到这里我们来看代码
代码
class Solution {public boolean canPartition(int[] nums) {//将数组分成两个子集,并且两个子集的元素和相等,就是把数组平均分成了两份//求出数组的总和sum,得到sum/2,判断int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}//如果sum和是奇数说明不能平均分成两个子数组,直接返回falseif (sum % 2 == 1) {return false;}//我们只需要判断能否凑出target,因为sum分成两个等份,target是其中一份,target满足则另一半一定满足int target = sum / 2;//dp数组的含义:下标i表示所有target所有可能的取值,dp[i]表示和为i的子集是否存在,长度加1是为了放索引0//只需要判断0——target位置boolean[] dp = new boolean[target + 1];/* //初始化数组,全设为false,因为暂时还不确定,但是boolean数组初始默认就是false,无需手动赋值for (int i = 0; i < dp.length; i++) {dp[i] = false;}*///dp[0]=true;和为0的子数组就是空集dp[0] = true;//遍历nums数组中的每一个数for (int i = 0; i < nums.length; i++) {//内层循环只需遍历到target,因为后面的数都大于target不满足条件for (int j = target; j >= nums[i]; j--) {// 状态转移逻辑:// 若不选当前元素nums[i]时,dp[j]已经为true(能凑出j),则保持true;// 若选当前当前元素nums[i]时,之前能凑出j - nums[i](即dp[j - nums[i]]为true),// 则加上当前元素后就能凑出j,因此dp[j]更新为true。if (dp[j] || dp[j - nums[i]]) {dp[j] = true;}}}return dp[target];}
}
好了,那今天的代码就写到这里了,我们明天见!
