leetcode 416.分割等和子集(回溯解法以及dp 解法)

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

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

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

示例 2:

输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

本题我的第一想法就是回溯,利用晦回溯遍历整个数组的所有子数组,观察子数组的和是否是数组和的1/2。 在利用回溯进行选择的时候,对"选择"(数组)往往先通过排序,利用有序性,可以帮助剪枝,也可以帮助去重。两种回溯的写法如下:

bool comp(int & a, int & b){
    return a > b;
}

class Solution {
public:
    bool DFS(vector<int> & nums, int ptr, int curr_sum, int target)
    {
        int remain = target - curr_sum;
        for (int i = ptr; i < nums.size(); i++)
        {
            if (nums[i]==remain)
                return true;
            else if (nums[i] < remain)
            {
                if (DFS(nums, i+1, curr_sum+nums[i], target))
                    return true;
            }
            //nums[i] > remain的情况不用管,直接下一步
        }
        return false;//全部都搜过一遍还是没有return true
    }

    bool canPartition(vector<int>& nums)
    {
        int n = nums.size();
        int tot_sum = 0;
        for (int i = 0; i < n; i++)
        {
            tot_sum += nums[i];
        }
        if (tot_sum % 2 != 0)
            return false;
        int div_sum = tot_sum / 2;

        sort(nums.begin(), nums.end(), comp);//降序排列
        if (nums[0] > div_sum) return false;//第一个就已经过半了
        else {
            if (DFS(nums, 0, 0, div_sum))
                return true;
            else return false;
        }
    }
};
class Solution {
public:
    bool DFS(int target,vector<int>& nums,int j)
    {
        if(target==0)
            return true;
        if(j==nums.size())
            return false;
        if(target<0)
            return false;
        return DFS(target-nums[j],nums,j+1)||DFS(target,nums,j+1);
    }
    bool canPartition(vector<int>& nums) {
         int sum=accumulate(nums.begin(),nums.end(),0);
         sort(nums.rbegin(),nums.rend());
         int target=sum/2;
         if(sum%2==1)
             return false;
         if(nums[0]>target)
             return false;
         if(nums[0]==target)
             return true;
         return DFS(target,nums,0);
    }
};

这道题目可以使用动态规划的思想,创建二维数组 \(dp\),包含 \(n\)\(target+1\) 列,其中 \(dp[i][j]\) 表示从数组的 \([0,i]\)(左闭右闭) 下标范围内选取若干个正整数(可以是 \(0\) 个),是否存在一种选取方案使得被选取的正整数的和等于 \(j\)。初始时,\(dp\) 中的全部元素都是 \(false\)

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return false;
        }
        int sum = accumulate(nums.begin(), nums.end(), 0);
        int maxNum = *max_element(nums.begin(), nums.end());
        if (sum & 1) {
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }
        vector<vector<int>> dp(n, vector<int>(target + 1, 0));
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        dp[0][nums[0]] = true;
        for (int i = 1; i < n; i++) {
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][target];
    }
};
posted @ 2020-10-11 14:42  wsl-hitsz  阅读(423)  评论(0编辑  收藏  举报