AC截图
题目
思路
dp[i][j]
的定义
-
i:表示当前考虑的是数组中前
i
个元素(从0开始计数)。也就是说,i
代表了我们当前可以使用的元素范围是从第一个元素到第i
个元素。 -
j:表示我们尝试达到的目标和。这个值从
0
到target
(其中target
是数组总和的一半)。
因此,dp[i][j]
是一个布尔值,表示是否可以通过选择数组中前i+1
个元素中的某些元素,使得这些元素的和恰好等于j
。
初始化
-
对于所有的
i
,设置dp[i][0] = true
,这是因为对于任何一组元素,都存在一种情况即不选取任何元素,此时元素的和为0
。 -
对于
dp[0][nums[0]]
,如果nums[0] <= target
,则设置dp[0][nums[0]] = true
,因为第一个元素本身就能构成和为nums[0]
的子集。
状态转移方程
对于每个元素nums[i]
,我们需要更新dp
数组以反映包含或不包含当前元素的所有可能性。具体的状态转移方程如下:
-
如果目标和
j
大于等于当前元素num
(即nums[i]
),则有两种情况:-
不选择当前元素:
dp[i][j] = dp[i-1][j]
-
选择当前元素:
dp[i][j] = dp[i-1][j-num]
。这意味着如果我们选择了当前元素,则需要检查剩余部分(即j-num
)是否可以通过前i-1
个元素来实现。
因此,状态转移方程为:
dp[i][j] = dp[i-1][j] | dp[i-1][j-num]
。 -
-
如果
j < num
,则不能选择当前元素,只能依赖不选择当前元素的情况:dp[i][j] = dp[i-1][j]
。
最终结果
最终的结果是dp[n-1][target]
,其中n
是数组的长度,target
是数组总和的一半。如果dp[n-1][target]
为true
,则表示可以找到一个子集,其元素之和等于target
,从而可以将原数组分割成两个和相等的子集。
实例
以nums = [1,5,11,5]为例
nums[i]/j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
nums[0] = 1 | T | T | F | F | F | F | F | F | F | F | F | F |
nums[1] = 5 | T | T | F | F | F | T | T | F | F | F | F | F |
nums[2] = 11 | T | T | F | F | F | F | F | F | F | F | F | T |
nums[3] = 5 | T | T | F | F | F | T | T | F | F | F | F | T |
代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
if(nums.size()<2) return false;
int sum = 0;
int mx = nums[0];
for(int i=0;i<nums.size();i++){
sum += nums[i];
if(mx<nums[i]){
mx = nums[i];
}
}
if(sum%2!=0) return false;
int target = sum/2;
if(mx>target) return false;
int n = nums.size();
vector<vector<bool>> dp(n,vector<bool>(target+1));
// 对于前i个正整数,j=0代表一个正整数都不取,是可以做到的
for(int i=0;i<n;i++){
dp[i][0] = true;
}
// 对于第一个正整数,如果放下,就是dp[0][nums[0]]=true
dp[0][nums[0]] = true;
for(int i=1;i<n;i++){
int num = nums[i];
for(int j=1;j<=target;j++){
//当前正整数小于等于目标值,可以考虑放入/不放入
if(num<=j){
dp[i][j] = dp[i-1][j] || dp[i-1][j-num];
}else{
//当前正整数大于目标值,不能放入
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][target];
}
};