题目
416. 分割等和子集
中等
相关标签
数组 动态规划
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
思路一二都是基于动态规划来解释的。
不会的可以先去了解了解动态规划是什么,怎么做,模板为什么,等等
然后去了解纯01背包的问题。可以上B站搜,或者本站csdn搜索
这里提供B站视频
代码随想录 动态规划 理论基础https://www.bilibili.com/video/BV13Q4y197Wg/?spm_id_from=333.337.search-card.all.click&vd_source=fa18c7bb110345953d1f71c2974a5da6代码随想录 01背包 解题方法https://www.bilibili.com/video/BV1cg411g7Y6/?spm_id_from=333.788&vd_source=fa18c7bb110345953d1f71c2974a5da6代码随想录 01背包 进阶解法 https://www.bilibili.com/video/BV1BU4y177kY/?spm_id_from=333.788&vd_source=fa18c7bb110345953d1f71c2974a5da6
思路和解题方法 一 (二维数组dp法)
首先,计算数组
nums
的元素和,并判断是否为奇数,如果是,则无法平分成两个子集,直接返回false
。计算目标和
target
,即每个子集的和应该为sum / 2
,同时判断数组nums
中的最大值是否大于target
,如果是,则无法平分,直接返回false
。初始化一个大小为
n * (target+1)
的二维数组dp
,其中dp[i][j]
表示在前i
个元素中是否能够找到一些元素的和等于j
。将第一列
dp[i][0]
全部初始化为true
,因为任何元素都可以不选,使其和为 0。将第一行
dp[0][nums[0]]
初始化为true
,因为只有第一个元素可以被选中,使其和为nums[0]
。从第二个元素开始遍历数组
nums
,对于每个元素nums[i]
,从 1 到目标和target
进行遍历,计算dp[i][j]
的值。如果当前元素的值
nums[i]
大于当前的和j
,则不能将该元素加入到和为j
的子集中,因此dp[i][j]
的值与dp[i-1][j]
相等。如果当前元素的值
nums[i]
小于等于当前的和j
,则可以将该元素加入到和为j
的子集中。此时,需要考虑两种情况:选中当前元素或不选中当前元素。因此,dp[i][j]
的值等于dp[i-1][j]
或dp[i-1][j-nums[i]]
。循环结束后,返回
dp[n-1][target]
的值,表示在前n
个元素中是否能够找到一些元素的和等于目标和target
。
复杂度
时间复杂度:
O(n*target)
时间复杂度:
- 初始化动态规划数组:O(n * target),其中 n 是数组的长度,target 是目标和的大小。
- 动态规划过程中的两层循环:O(n * target)。
因此,总体时间复杂度为 O(n * target)。
空间复杂度
O(n*target)
空间复杂度:
- 动态规划数组:O(n * target),用于存储子问题的解。
因此,总体空间复杂度为 O(n * target)。
c++ 代码 一
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n = nums.size();
if (n < 2) {
return false;
}
int sum = accumulate(nums.begin(), nums.end(), 0); // 计算数组元素的总和
int maxNum = *max_element(nums.begin(), nums.end()); // 找到数组中的最大值
if (sum & 1) { // 如果总和为奇数,则无法平分成两个子集
return false;
}
int target = sum / 2; // 目标和为总和的一半
if (maxNum > target) { // 如果最大值大于目标和,则无法平分
return false;
}
vector<vector<int>> dp(n, vector<int>(target + 1, 0)); // 创建二维动态规划数组
for (int i = 0; i < n; i++) {
dp[i][0] = true; // 初始化第一列,表示任何元素都可以不选,使其和为0
}
dp[0][nums[0]] = true; // 初始化第一行,只有第一个元素可以被选中,使其和为nums[0]
for (int i = 1; i < n; i++) { // 从第二个元素开始遍历数组
int num = nums[i];
for (int j = 1; j <= target; j++) { // 遍历目标和的范围
if (j >= num) { // 如果当前和大于等于当前元素的值
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]; // 返回最后一个元素对应的状态,表示是否能够找到一些元素的和等于目标和
}
};
思路和解题方法 二(压缩二维 ☞ 一维)
首先,定义了一个类
Solution
,其中包含了一个公有成员函数canPartition
,该函数接受一个整数数组nums
,并返回一个布尔值。在函数内部,首先计算了数组
nums
中所有元素的和,并将结果存储在变量sum
中。接下来,通过判断
sum
是否为奇数,来确定是否可以平分数组。如果sum
是奇数,则无法平分,直接返回false
。如果
sum
是偶数,说明可以进行平分。将sum
除以 2 得到目标值target
,即每个子集的和应该为target
。初始化一个大小为 10001(为题目数据大小*长度 / 2) 的数组
dp
,用于存储动态规划过程中的中间结果。数组dp
的索引表示当前的和,数组的值表示是否可以组成该和。接下来,使用两层循环遍历数组
nums
和目标值target
。外层循环遍历数组nums
,内层循环从target
开始递减到当前元素的值nums[i]
。在内层循环中,更新数组
dp
的值。对于每个位置j
,比较dp[j]
和dp[j-nums[i]] + nums[i]
的大小,取较大值作为dp[j]
的新值。这表示在考虑当前元素nums[i]
时,可以得到的最大和为dp[j]
。循环结束后,判断数组
dp
的最后一个元素dp[target]
是否等于target
。如果是,则说明可以找到一个子集使其和为target
,返回true
;否则,返回false
。
复杂度
时间复杂度:
O(n*target)
时间复杂度为 O(n×target),其中 n 是数组
nums
的长度,target
是数组nums
中所有元素的和的一半。因为在内层循环中,需要遍历从target
到nums[i]
的所有可能的和,所以时间复杂度是 O(target),外层循环需要遍历整个数组nums
,所以时间复杂度是 O(n),因此总的时间复杂度是 O(n×target)。
空间复杂度
O(target)
这段代码的空间复杂度为 O(target),因为需要创建一个大小为
target+1
的数组dp
来存储动态规划的中间结果。
c++ 代码 二
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0); // 计算数组 nums 的所有元素的和
if (sum % 2 == 1) { // 如果和为奇数,则无法平分成两个子集,直接返回 false
return false;
}
int target = sum / 2; // 计算目标和,即每个子集的和应该为 sum / 2
vector<bool> dp(target + 1, false); // 初始化一个大小为 target + 1 的布尔型数组 dp,表示能否组成目标和
dp[0] = true; // 初始状态:目标和为 0 时肯定可以组成
for (int i = 0; i < nums.size(); ++i) { // 遍历数组 nums
for (int j = target; j >= nums[i]; --j) { // 从 target 开始递减到 nums[i]
dp[j] = dp[j] || dp[j - nums[i]]; // 更新 dp[j] 的值
}
}
return dp[target]; // 返回 dp[target] 的值,表示是否能够组成目标和
}
};
觉得有用的话可以点点赞,支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。