每周LeetCode算法题(五):416. Partition Equal Subset Sum

每周LeetCode算法题(五)

题目: 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:
Each of the array element will not exceed 100.
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.

解法分析

题目的要求是,给定一个数组nums,判断里面的元素能不能分成和相同的两部分。
我的做法是,先求和,若数组的和本身就是奇数,那绝不可能被分成和相同的两部分,故返回false。若和为偶数,则进一步判断。
将和除以2,设为target,即现在我们的目标是从数组中抽出元素,看看能不能使它们的和恰为target,即全部元素的和的一半。
这里会用到动态规划的思想,以及空间换时间。
用一个长度为target+1的bool型数组reach来记录数组内抽出的任意数量的元素的和能达到多少,若能达到i,则reac[i]设为true。当整个判断过程结束,reach[target]上存的值就是最终结果。
具体一点,我们针对每个nums里的元素nums[i],尝试给它加上一个从0开始的数j,如果表示的由i前面某些元素相加的和j已经存在(即reach[j]值为true),那么再加上一个nums[i]一定会使reach[j + nums[i]]值成为true。
但这样有个问题是,在这以后进行的第j’ = j + nums[i]轮里,因为reach[j + nums[i]]为true,那么reach[j’ + nums[i]]也会为true,尽管实际上这个和可能并不存在。
所以我们还需要一个相同大小的数组pre,用来记录i的上一轮循环后reach的状态,不让reach在同一次循环中错误地将一次修改的值再传播到后面的多次中去。
说得可能不是很清楚,还是上代码吧。

C++代码

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;
        bool *reach = new bool[target + 1];
        bool *pre = new bool[target + 1];
        memset(reach, false, (target + 1) * sizeof(bool));
        memset(pre, false, (target + 1) * sizeof(bool));
        pre[0] = reach[0] = true;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j + nums[i] <= target; j++) {
                if (pre[j] == true) {
                    reach[j + nums[i]] = true;
                }
            }
            for (int j = 0; j <= target; j++) {
                pre[j] = reach[j];
            }
        }
        return reach[target];
    }
};

解法优化

为了避免上面的麻烦,对reach的遍历稍作了修改,j的循环改成从后往前,避免了多开一个数组存上一步的状态,效率稍稍提升了一点。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;
        bool *reach = new bool[target + 1];
        memset(reach, false, (target + 1) * sizeof(bool));
        reach[0] = true;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = target - nums[i]; j >= 0; j--) {
                reach[j + nums[i]] = reach[j + nums[i]] || reach[j];
            }
        }
        return reach[target];
    }
};

你可能感兴趣的:(leetcode,leetcode)