Partition Equal Subset Sum 分割等和子集

给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

  1. 每个数组中的元素不会超过 100
  2. 数组的大小不会超过 200

示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

思路:

这道题其实可以变成是0 1 背包问题,申请二维数组dp[i][j](0<=i<=nums.size(),0<=j<=(sum/2),sum是数组的和的一半),要划分成两半且和相等,即sum(left)=sum(right),那么原数组和必须是偶数,否则无法划分。其中dp[i][j]表示从第一个元素到第i个元素是否存在能组成和为j的子集,如果可以为true,否则为false。

接下来我们来看递推公式,看看dp[i][j]可以怎么由子问题推导而来,先给出公式:

dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];

1:如果不考虑第i个元素,那么情况等于前i-1个元素的情况即dp[i-1][j](前i-1个元素如果已经可以划分子集左,那么剩下的元素直接划分到另外一边即子集右即可

2:如果考虑第i个元素,那么情况等于前i-1个元素的子集和加上第i个元素的和可以组成和j,j-nums[i]表示前i-1个元素可以组成和为j-nums[i],那么加上第i个元素nums[i],和即可j,可以组成子集。

所以是这两种情况的或构成递推公式,我一直觉得这道题和背包问题的理解有点出入,因为01背包问题是背或者不背,但一定所有的元素最后都会有结果(背或者不背),而这道题元素的背指的在左半边子集,不背指的在右半边子集,我们的目标是使得左半边子集的和等于总和的一半。这样思考才会和背包问题对应上。

二维数组版:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
	bool res = false;
	int sum = 0;
	int m = nums.size();
	for (int i = 0; i < m; i++) {
		sum += nums[i];
	}
	if ((sum & 1) == 1) {
		return false;
	}
	sum /= 2;
	bool **dp = new bool *[nums.size() + 1];
	for (int i = 0; i <= m; i++) {
		dp[i] = new bool[sum + 1];
	}
	for (int i = 0; i <= m; i++) {
		dp[i][0] = true;
	}
	for (int i = 1; i <= sum; i++) {
		dp[0][i] = false;
	}
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= sum; j++) {
			if (j >= nums[i - 1]) {
				dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
			}
			else {
				dp[i][j] = dp[i - 1][j];
			}
		}
	}
	res = dp[m][sum];

	for (int i = 0; i <= m; i++) {
		delete[] dp[i];
	}
	delete[] dp;
	return res;        
    }
};

我们把空间复杂度优化到一维,观察到对于内循环:

for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= sum; j++) {
			if (j >= nums[i - 1]) {
				dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
			}
			else {
				dp[i][j] = dp[i - 1][j];
			}
		}
	}

我们其实只需要上一层i-1的状态,所以我们把二维数组压缩为一维,递推公式如下:

dp[j] =dp[j] || dp[j - nums[i - 1]];    j>=nums[i-1]

这里还需要注意一点,内层循环需要从大到小,避免被覆盖。

参考代码:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
	bool res = false;
	int sum = 0;
	int m = nums.size();
	for (int i = 0; i < m; i++) {
		sum += nums[i];
	}
	if ((sum & 1) == 1) {
		return false;
	}
	sum /= 2;
	bool *dp = new bool [sum + 1];
	for (int i = 1; i <= sum; i++) {
		dp[i] =false;
	}
	dp[0] = true;

	for (int i = 0; i < m; i++) {
		for (int j = sum; j > 0; j--) {
			if (j >= nums[i]) {
				dp[j] =dp[j] || dp[j - nums[i]];
			}
		}
	}
	res = dp[sum];

	delete[] dp;
	return res; 
    }
};




  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值