17-今日一扣(LeetCode)-416-DP01背包-M-494-DP01背包-M

416. 分割等和子集-M

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

注意:

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

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

题解

思路:

向01背包问题转化。本题虽然是分成两个大小相同的组。也就是说,每个组元素的和应该是sum(nums)/2。

那么我们的问题就可以转化为:从nums数组里找一些数,使他们的和为sum(nums)/2。我们记这个和为target = sum(nums)/2

和背包问题的不同指出:

  • 背包问题是希望我们的总和最大。所以 d p [ i ] [ j ] dp[i][j] dp[i][j]记录的是前i个物品中进行选择,使其在容量j下,记录所能达到的价值最大值。
  • 此问题希望我们选取综合等于target。所以 d p [ i ] [ j ] dp[i][j] dp[i][j]记录的是前i个数进行选择,是他们的和为j。如果可以,则记录true。

状态转移方程:

注意nums[i-1]是代表第i个数的大小。因为nums数组和dp数组有个错位。
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] o r d p [ i − 1 ] [ j − n u m s [ i − 1 ] ] dp[i][j] = dp[i-1][j] \quad or \quad dp[i-1][j-nums[i-1]] dp[i][j]=dp[i1][j]ordp[i1][jnums[i1]]

二维dp数组解法:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        int n = nums.length;
        for(int i=0; i<n; i++){
            sum+=nums[i];
        }
        if(sum%2==1){
            return false;
        }
        int target = sum/2;
        
        //要把i = 0, j=0的行列都给空出来。所以要n+1维
        boolean[][] dp = new boolean[n+1][target+1];
        
        // for(int i = 0; i<=n; i++){
        //     dp[i][0] = true;
        // }
        dp[0][0] = true;

        for(int i=1; i<=n; i++){
            for(int j = 0; j<=target; j++){
                dp[i][j] = dp[i-1][j] ;
                //注意我们要处理第i个数,结果存放在dp[i][j]
                //第i个在nums中的下表是i-1
                if(nums[i-1]<=j){
                    dp[i][j] = dp[i][j] || dp[i-1][j-nums[i-1]];
                }
            }
        }
        return dp[n][target];
    }
}

空间复杂度优化:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        int n = nums.length;
        for(int i=0; i<n; i++){
            sum+=nums[i];
        }
        if(sum%2==1){
            return false;
        }
        int target = sum/2;
        
        boolean[] dp = new boolean[target+1];
        
        dp[0] = true;
        for(int i=1; i<=n; i++){
            for(int j = target; j>=nums[i-1]; j--){
                // dp[i][j] = dp[i-1][j] ;
                // if(nums[i-1]<=j){
                //     dp[i][j] = dp[i][j] || dp[i-1][j-nums[i-1]];
                // }
                dp[j] = dp[j] || dp[j-nums[i-1]];
            }
        }
        return dp[target];  //返回最后一个
    }
}

494. 目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

提示:

数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。

题解

仍然是一个01背包问题。

对于每个数字,都有+/-两种选择。我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]记录前i个元素,总和为j的时候的方法数量。

递推公式:
d p [ 0 ] [ 0 ] = 1 , 表 示 0 个 数 总 和 为 0 时 , 只 有 一 个 钟 方 案 。 d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] + d p [ i − 1 ] [ j + n u m s [ i ] ] dp[0][0] =1, 表示0个数总和为0时,只有一个钟方案。\\ dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]] dp[0][0]=100dp[i][j]=dp[i1][jnums[i]]+dp[i1][j+nums[i]]
由于总和不超过1000,所以我们计算的j的范围从-1000到1000。 由于数组不能有负的索引,故把-1000到+1000映射到了0-2001

class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int n = nums.length;
        int [][]dp = new int[n+1][2000+3];
        if(S>1000){
            return 0;
        }
        //初始化,注意这里没有引用第0维,所以不能初始化为0
        
        dp[0][1000] = 1;
        for(int i=1; i<=n; i++){
            for(int j = 0; j<=2000; j++){
                if(j-nums[i-1]>=0 && j+nums[i-1] <=2000){
                    dp[i][j] = dp[i-1][j-nums[i-1]] + dp[i-1][j+nums[i-1]]; 
                }else if(j-nums[i-1]<0){
                    dp[i][j] = dp[i-1][j+nums[i-1]];
                }else if(j+nums[i-1]>2000){
                    dp[i][j] = dp[i-1][j-nums[i-1]];
                }
            }
        }
        return dp[n][1000+S];
    }
}

空间复杂度优化到O(2n)

class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int n = nums.length;
        int []dpPre = new int[2000+1];
        if(S>1000){
            return 0;
        }
        //初始化,注意这里没有引用第0维,所以不能初始化为0       
        dpPre[1000] =1;
        for(int i=1; i<=n; i++){
            int []dp = new int[2000+1];
            for(int j = 2000; j>=0; j--){
                if(j-nums[i-1]>=0 && j+nums[i-1] <=2000){
                    dp[j] = dpPre[j-nums[i-1]] + dpPre[j+nums[i-1]]; 
                }else if(j-nums[i-1]<0){
                    dp[j] = dpPre[j+nums[i-1]];
                }else if(j+nums[i-1]>2000){
                    dp[j] = dpPre[j-nums[i-1]];
                }
            }
            dpPre = dp;
        }
        return dpPre[1000+S];
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值