力扣416:分割等和子集
题目
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路
我们先从最简单的思路开始想起,一个数组想要分成两个子集并且子集和相同那么这个数组的和必须是奇数并且数组的大小必须大于2。所以我们可以先对这两个进行判断,其次在求数组和时我们还可以求一下数组的最大元素,然后我们把数组和除2也就得到了两个子集的和target,我们再用子集和和数组的最大元素进行比较,如果最大元素已经大于子集和了也就可以返回false了因为最大元素已经大于子集和了其他的所有元素加起来都不可能等于子集和了。
那么在对特殊情况进行判断也就是剪枝之后我们就可以判断正常情况了,在去掉前面的情况后现在的题目就变成了在数组中是否有一个子序列的序列和为target。在进行问题转换后我们可以来思考一个问题,我们是不是可以把这个问题拆分开来也就是形成一个一个的子问题,那么子问题是什么呢?我们设置i为数组的下标,j为目标值,子问题就是数组的前i个数字是否有一种选法让和为j。那么这个i的范围就是[0,nums.size()),j的范围就是[0,target],所以我们可以定义一个二维数组dp,dp[i][j]就是数组nums的前i个元素是否有一种选择方式可以让和为j,如果有就设为true没有就设为false,有子问题有dp数组这不就是动态规划吗。
在定义了dp数组后我们先来判断边界问题也就是i和j分别为0的情况,当i为0的时说明我们只有一个数字可以选择所以dp[0][nums[0]] = true,当j为0时目标值为0所以无论i为多少我们肯定能得到目标值所以dp[i][0] = true。
在解决了边界问题后我们就要解决普遍情况了,也就是推导状态转移方程即dp[i][j] = ?,我们有两个值可以拿来用一个是当前值nums[i]一个是目标值j,这两个值之间有什么关系吗。当然是有的,如果当前值大于目标值说明这个值不能被选,如果当前值小于目标值说明这个值可以被选也可以不被选,总的来说一个值只有选和被选两种情况。
- 被选
当一个值要被选择时,我们想要判断选上这个数字nums[i]后是否等于j就需要考虑上一个数字nums[i-1]以及目标值j-nums[i]。因为我们选上这个数后前面的子序列的和必须是j-nums[i]。
所以一个数被选择后
d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i][j] = dp[i-1][j-nums[i]] dp[i][j]=dp[i−1][j−nums[i]] - 不被选
当一个值不被选时我们就只需要考虑前一个数字和当前目标值的是否满足条件了
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
所以总的状态转移方程是
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − n u m s [ i ] ] ∣ d p [ i − 1 ] [ j ] ( j > = n u m s [ i ] ) d p [ i − 1 ] [ j ] ( j < n u m s [ i ] ) } dp[i][j] = \begin{Bmatrix}dp[i-1][j-nums[i]]| dp[i-1][j] (j >= nums[i]) \\dp[i-1][j](j < nums[i])\end{Bmatrix} dp[i][j]={dp[i−1][j−nums[i]]∣dp[i−1][j](j>=nums[i])dp[i−1][j](j<nums[i])}
代码
cpp
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n = nums.size();
if (n < 2) {
return false;
}
int total = 0;
int maxnumber = INT_MIN;
for (int i = 0; i < n; i++) {
total += nums[i];
maxnumber = max(maxnumber, nums[i]);
}
// 数组和为奇数则不可能分为两个子集
if (total % 2 != 0) {
return false;
}
int target = total / 2;
// 如果最大值小于数组和的一半就说明其他的数加起来也不可能等于最大的数
if (maxnumber > target) {
return false;
}
// n行,target+1列
// 行代表是数组的前几个数
// 列代表的是目标值是多少
// dp[i][j]里存储的就是这前几个数有没有可能和为目标值
vector<vector<int>> dp(n, vector<int>(target + 1, 0));
// 列为0时目标值为0,当然有可能
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
// 行为0时只有一个数可以被选,对应的位置为true
dp[0][nums[0]] = true;
// 非特殊位置
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
// 判断j和num[i]的大小关系
if (j >= nums[i]) {
// 如果j大于等于nums[i]就说明这个数字是可以被选也可以不被选的
// 被选时dp[i][j]的值就取决于dp[i-1][j-nums[i]]
// 不被选dp[i][j]的值取决于dp[i-1][j]
dp[i][j] = dp[i - 1][j - nums[i]] | dp[i - 1][j];
} else {
// j小于nums[i]说明这个值不能被选
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
};