1. 程式人生 > >LeetCode416. Partition Equal Subset Sum

LeetCode416. Partition Equal Subset Sum

416. Partition Equal Subset Sum

Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Note:

  1. Each of the array element will not exceed 100.
  2. The array size will not exceed 200.

Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.

題目:根據所給的一串元素,全為正數,判斷是否能分為兩個和相等的子集。

首先看到題目,首先第一直覺就是既然要求兩個相等的子集,那麼就可以先求出整個陣列的和sum,然後在陣列中選擇若干元素是否能滿足和為sum/2即可。

關鍵是怎麼取元素,取多少個。簡單的想法就是遍歷所有的情況,用DFS。對每個元素分別考慮取和不取時的情況,當所有元素都判斷過,則返回。但是在執行時間上超過了限制。Time Limits Exceeded。

#include <iostream>
#include<vector>
#include<algorithm>
#include<numeric>
using namespace std;

class Solution3 {
public:
	bool canPartition(vector<int>& nums) {
		if (nums.size() == 1)
			return false;

		int sum = accumulate(nums.begin(), nums.end(), 0);
		if (sum % 2 != 0)
			return false;
		//只需找到所有元素和的一半 對應的組合即可
		sum /= 2;

		int result = DFS(nums, sum, 0);
		return result;
	}

private:
	int DFS(vector<int>& nums, int remain, int start) {
		if (start == nums.size())
			return remain == 0 ? 1 : 0;

		if (remain < 0)
			return 0;

		return DFS(nums, remain - nums[start], start + 1)
			+ DFS(nums, remain, start + 1);
	}
};

如果我們要判斷n個元素中的子集是否能組成和為sum,那麼對於nums中的每個元素,我們都需要考慮選擇和不選擇該元素時對sum的影響,假設現在在判斷第i個元素,此時前i-1個元素所能組成的和的集合記為sum(i-1) = {sum1,sum2,...},如果取nums[i]時,那麼sum(i) = {sum1+nums[i], sum2+nums[2],....},如果不取nums[i],那麼sum(i)=sum(i-1) = {sum1,sum2,...},最終對於第i個元素,我們的和的集合更新為sum(i)={sum1,sum2,sum1+nums[i], sum2+nums[2],...}。

以上的分析造成集合sum(i)的大小是在不斷擴大的,由於題目的特殊性,所有的元素均為正數,由此我們知道集合sum(i)中的元素肯定也都是正數,同時我們需要求的是在sum(i)這個集合中是否存在target,因此我們可以事先將sum集合的範圍設定為[0,target],如果前面集合出現了超過target的和,那麼這個和對於我們來說是沒用的,因為後面的元素在這個和的基礎上進行操作的話必然也是會大於target的。

因此可以設定一個二維的陣列dp[nums.size()+1][target+1],dp[i][j]表示前i個元素是否是組合成和為j,如果能的話則值為true,否則為false。

dp[i][j] = dp[i-1][j]

dp[i][j] = dp[i-1][j-nums[i]].

https://github.com/abesft/leetcode/blob/master/416PartitionEqualSubsetSum/416PartitionEqualSubsetSum.cpp

#include <iostream>
#include<vector>
#include<algorithm>
#include<numeric>
using namespace std;


class Solution {
public:
	bool canPartition(vector<int>& nums) {
		if (nums.size() == 1)
			return false;

		int sum = accumulate(nums.begin(), nums.end(), 0);
		if (sum % 2 != 0)
			return false;
		//只需找到所有元素和的一半 對應的組合即可
		sum /= 2;

		//dp[i][j]表示當前陣列中的前i個元素是否能組成和為j
		vector<vector<bool>> dp(nums.size() + 1, vector<bool>(sum + 1));

		dp[0][0] = true;

		for (int i = 1; i < nums.size() + 1; i++) {
			dp[i][0] = true;
		}

		for (int j = 1; j < sum + 1; j++) {
			dp[0][j] = false;
		}

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

		return dp[nums.size()][sum];
	}
};

class Solution2 {
public:
	bool canPartition(vector<int>& nums) {
		if (nums.size() == 1)
			return false;

		int sum = accumulate(nums.begin(), nums.end(), 0);
		if (sum % 2 != 0)
			return false;
		//只需找到所有元素和的一半 對應的組合即可
		sum /= 2;

		//dp[i]表示當前陣列中是否有相應的組合的和的值為i
		vector<int> dp(sum + 1, false);

		dp[0] = true;

		for (size_t i = 0; i < nums.size(); i++)
		{
			//注意這裡是反向
			for (int j = sum; j > 0; j--)
				if (j >= nums[i])
					dp[j] = dp[j] || dp[j - nums[i]];
		}

		return dp[sum];
	}
};

int main()
{
	Solution sln;
	vector<int> testcase{ 1,2,5 };
	cout << sln.canPartition(testcase) << endl;
	std::cout << "Hello World!\n";
}

參考:https://leetcode.com/problems/partition-equal-subset-sum/discuss/90592/01-knapsack-detailed-explanation