力扣: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

二、问题分析

  先假设sum为数组的总和,需要确定是否可以将这个数组分割成两个元素和相等的子集,只要集合中出现总和sum/2的子集,就可以算这个数组可以分割成两个元素和相等的子集。也就是从数组从选出部分元素,使元素总和恰好为sum/2,推导到这里就可以用动态规划的经典理论0-1背包来解决问题。

  • 确定dp数组及下标的含义: d p [ i ] dp[i] dp[i]表示总容量是 i i i的背包最大可以凑成的总和
  • 确定递推公式:相当于在背包中放入数值,而且物品 i i i的重量是 n u m s [ i ] nums[i] nums[i],其价值也是 n u m s [ i ] nums[i] nums[i] d p [ i ] dp[i] dp[i]可以从 d p [ j − n u m s [ i ] ] + n u m s [ i ] dp[j-nums[i]]+nums[i] dp[jnums[i]]+nums[i],所以递推公式:dp[i]=max(dp[i],dp[j-nums[i]]+nums[i])
  • 初始化dp数组,题目中给出价值都是正整数,那么非0下标的数组都初始化为0即可。题目提示中说:每一个数组中的元素不会超过100,数组的长度不会超过200,那么总和不会大于20000,背包的最大容量只需要其中一半,所以10001就足够了。
  • 确定遍历顺序:0-1背包使用一维数组,遍历物品的for循环放在外层,遍历背包的for循环放在内层,且内层for循环使用倒叙遍历。

三、代码实现

// 编程软件:VS2019
// 参考书籍:代码随想录
#include
#include
#include
using namespace std;

// 时间复杂度O(n的平方),空间复杂度O(n)
bool canPartition(vector<int>& nums) {
	int sum = 0;
	vector<int> dp(10001, 0);
	// 计算数组总和
	for (int i = 0; i < nums.size(); i++) { 
		sum += nums[i];
	}
	// 如果数组总和为奇数,那么是不可能分成两个元素和相等的子集,直接返回false
	if (sum % 2 == 1) return false; 
	int target = sum / 2; 
	// 0-1背包逻辑
	for (int i = 0; i < nums.size(); i++) {
		// 每一个元素一定不能重复放入背包,所以for循环是从大到小遍历的
		for (int j = target; j >= nums[i]; j--) {
			dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
		}
	}
	// 判断是否恰好为target
	if (dp[target] == target) return true;
	return false;
}

int main() {
	vector<int>nums = { 1,5,11,5 };
	if (canPartition(nums))
		cout << "true";
	else
		cout << "false";
}
// 结果:true

四、小结

  要学会从题目的要求出发,逆向的推导问题,将问题本质与学过知识联系。就像本题,从是否可将数组分割成两个元素和相等的子集出发,推导出从数组选取部分元素,而这些元素之和若恰好为 sum/2,则说明符合题目要求,这就与 0-1背包碰上了。

你可能感兴趣的:(leetcode,c++,动态规划,leetcode)