【动态规划01背包】leetcode 494.目标和


image.png
这道题其实是一个01背包问题,只是加了一点的小变形而已
现在考虑:
怎么可以将这个变为从n个数中挑选出target个重量的组合数

可以将数分为两组,一组是符号是+的为left,一组是符号为-的为right
sum=left+right
target=left-right
所以,我们真正的target就是left=(sum+target)/2,我们要做的就是从上面的数组中挑选出满足加起来的重量是target的组合数

二维数组版本

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        if(nums==null||nums.length==0)
            return 0;
        int n=nums.length;
        //下面是计算出真正的target:
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
        }
        //特殊情况:
        if(target>sum||target<-1*sum||(target+sum)%2==1)
            return 0;
        target=(target+sum)/2;
        int[][] f=new int[n+1][target+1];
        f[0][0]=1;
        for(int i=1;i<=target;i++){
            f[0][i]=0;
        }
        for(int i=1;i<=n;i++){
            for(int j=0;j<=target;j++){
                f[i][j]=f[i-1][j];
                if(j>=nums[i-1])
                    f[i][j]+=f[i-1][j-nums[i-1]];
            }
        }
        return f[n][target];
    }
}

二维数组升级版

上面的空间复杂度是O(MN)
但是我们还可以进行简化成f[2][N]

我们观察到f[i][j]只是和它的上一层的f[i-1][j]和f[i-1][j-nums[i-1]]有关
所以说我们就可以只开辟一个2*n的数组进行滚动就可以了

这里我们使用old和new1来表示上一层和本层,这样就简化了时间复杂度

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        if(nums==null||nums.length==0)
            return 0;
        int n=nums.length;
        //下面是计算出真正的target:
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
        }
        //特殊情况:
        if(target>sum||target<-1*sum||(target+sum)%2==1)
            return 0;
        target=(target+sum)/2;
        int[][] f=new int[2][target+1];
        f[0][0]=1;
        for(int i=1;i<=target;i++){
            f[0][i]=0;
        }
        int old=0,new1=0;
        for(int i=1;i<=n;i++){
            old=new1;
            new1=1-old;
            for(int j=0;j<=target;j++){
                f[new1][j]=f[old][j];
                if(j>=nums[i-1])
                    f[new1][j]+=f[old][j-nums[i-1]];
            }
        }
        return f[new1][target];
    }
}

一维数组版

上面的代码简化了不少,但是还可以更加的简洁,我们看一下转移方程:

f[new1][j]=f[old][j];
if(j>=nums[i-1])
    f[new1][j]+=f[old][j-nums[i-1]];

我们可以不必开两层数组,直接在上一层old中计算本层new1的值,
从后往前计算,因为f[old][j-nums[i-1]];我们需要用到的还是old的数据,所以为了不破坏,我们应该使用从后往前算.

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        if(nums==null||nums.length==0)
            return 0;
        int n=nums.length;
        //下面是计算出真正的target:
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
        }
        //特殊情况:
        if(target>sum||target<-1*sum||(target+sum)%2==1)
            return 0;
        target=(target+sum)/2;
        int[] f=new int[target+1];
        //这里就相当于i=0,前0个数字
        //前0个数字组成重量0可以有1种方法
        f[0]=1;
        //前0个数字不可以组成重量i
        for(int i=1;i<=target;i++){
            f[i]=0;
        }
        //遍历1~n个数字可以组成的重量
        for(int i=1;i<=n;i++){
            for(int j=target;j>=0;j--){
                if(j>=nums[i-1])
                    f[j]+=f[j-nums[i-1]];
            }
        }
        return f[target];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值