LeetCode 416. 分割等和子集

121 阅读1分钟
public:
    bool canPartition(vector<int>& nums) {
        int n  = nums.size(), sum = 0;
        for(auto x : nums) sum += x;
        if(sum % 2 != 0) return false;
        sum /= 2;
        vector<vector<bool>> f(n + 1, vector<bool> (sum + 1, false));//f[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。
        f[0][0] = true;
        for(int i = 1; i <= n; i ++) 
            for(int j = 0; j <= sum; j ++) {//j - nums[i] 作为数组的下标,一定得保证大于等于 0 ,因此 nums[i] <= j
                if(j < nums[i - 1]) f[i][j] = f[i - 1][j];
                else f[i][j] = f[i - 1][j] || f[i - 1][j - nums[i - 1]];
            }
        return f[n][sum];
    }
};
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size(), sum = 0;
        for(auto x : nums) sum += x;
        if(sum % 2 != 0) return false;
        int target = sum / 2;
        vector<int> f(sum + 1);
        for(int i = 0; i < n; i ++) 
            for(int j = target; j >= nums[i]; j --) {//「从后向前」 写的过程中,一旦 nums[i] <= j 不满足,可以马上退出当前循环,因为后面的 j 的值肯定越来越小,没有必要继续做判断,直接进入外层循环的下一层。相当于也是一个剪枝,这一点是「从前向后」填表所不具备的
                f[j] = max(f[j], f[j - nums[i]] + nums[i]);
            }

        if(f[target] == target) return true;//
        return false;
    }
};


【一维DP】内层循环倒序
//考虑到我我们在更新 dp[j]dp[j] 时,使用的其实是上一行的 dpdp 值;而如果第二层循环从小到大计算的话,那么 dp[j−nums[i-1]]dp[j−nums[i−1]] 先于 dp[j]dp[j] 被更新,因此当我们计算 dp[j]dp[j] 值的时候,dp[j−nums[i-1]]dp[j−nums[i−1]] 已经是被更新过的状态,而不再是上一行的 dpdp 值了。

而在第二层循环中,通过从大到小计算则可巧妙地保证在计算 dp[j]dp[j] 时所用到的 dp[j]dp[j] 和 dp[j-nums[i-1]]dp[j−nums[i−1]] 均来自上一行。

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!