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& 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& 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; 
    }
};




你可能感兴趣的:(算法)